From ca5b6f5ed187ceda0129427dc286566dbcb1c824 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Thu, 6 Aug 2026 18:13:08 +0000 Subject: [PATCH 01/14] working file --- R/helpers.R | 56 ++++++ pipeline/07-export.R | 419 ++++++++++++++++++++++++++++++------------- 2 files changed, 348 insertions(+), 127 deletions(-) diff --git a/R/helpers.R b/R/helpers.R index e6fcd00..ceb8d3b 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -318,3 +318,59 @@ create_rolling_origin_splits <- function(data, ) return(rset) } + + +# Schema helper functions for workbook creation -------------------------------- + +col_pos <- function(schema, col_name) { + which(names(schema) == col_name) +} + +cols_with_style <- function(schema, style_name) { + which(vapply(schema, function(x) identical(x$style, style_name), logical(1))) +} + +cols_with_cond <- function(schema, cond_name) { + which(vapply(schema, function(x) identical(x$cond, cond_name), logical(1))) +} + +cols_hidden <- function(schema) { + which(vapply(schema, function(x) isTRUE(x$hidden), logical(1))) +} + +validate_schema_vs_template <- function(schema, template_path, sheet_name, + header_row) { + normalize <- function(x) gsub("\\s+", " ", trimws(x)) + wb_tmpl <- loadWorkbook(template_path) + tmpl_df <- readWorkbook( + wb_tmpl, + sheet = sheet_name, + colNames = FALSE, + skipEmptyRows = FALSE + ) + tmpl_headers <- normalize(as.character(unlist(tmpl_df[header_row, ]))) + schema_names <- normalize( + vapply(schema, function(x) x$display_name, character(1)) + ) + n_schema <- length(schema_names) + n_tmpl <- length(tmpl_headers) + if (n_schema != n_tmpl) { + stop(glue::glue( + "Schema has {n_schema} columns but '{sheet_name}' template has ", + "{n_tmpl}. Update the schema or template so they match." + )) + } + mismatches <- which(schema_names != tmpl_headers) + if (length(mismatches) > 0) { + mismatch_msg <- paste(vapply(mismatches, function(i) { + glue::glue( + " Col {i}: schema='{schema_names[i]}'", + " vs template='{tmpl_headers[i]}'" + ) + }, character(1)), collapse = "\n") + stop(glue::glue( + "Column display name mismatches in '{sheet_name}':\n{mismatch_msg}" + )) + } + invisible(NULL) +} diff --git a/pipeline/07-export.R b/pipeline/07-export.R index a087a40..5a1bfe3 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -81,7 +81,206 @@ flag_assessable_permits <- dbGetQuery( #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# 3. Prep Desk Review ---------------------------------------------------------- +# 3. Define Workbook Schemas --------------------------------------------------- +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +# Each entry in the schema specifies: +# display_name - column header text (must match the template header row) +# style - named style from wb_styles (price, 2digit_price, 2digit_num, +# pct, comma, link) +# formula - TRUE if this column is written via writeFormula +# cond - named conditional-formatting group (color_scale, +# sale_outlier_1, sale_outlier_2) +# hidden - TRUE if the column is hidden in Excel (formula-only columns +# not present in the source data frame) + +pin_detail_schema <- list( + meta_pin = list(formula = TRUE, display_name = "PIN"), + meta_class = list(display_name = "Class"), + meta_nbhd_code = list(display_name = "Nbhd."), + property_full_address = list(display_name = "Street Address"), + loc_tax_municipality_name = list(display_name = "Municipality"), + meta_pin10 = list(formula = TRUE, display_name = "Condo Building ID (PIN10)"), + meta_tieback_key_pin = list(display_name = "Tieback Key PIN"), + meta_tieback_proration_rate = list( + style = "pct", display_name = "Tieback Percent Ownership" + ), + prior_near_land = list(style = "price", display_name = "Land"), + prior_near_bldg = list(style = "price", display_name = "Building"), + prior_near_tot = list(style = "price", display_name = "Total"), + prior_near_land_rate = list( + style = "2digit_price", display_name = "Lnd. Rate S. F." + ), + prior_near_bldg_rate = list( + style = "2digit_price", display_name = "Unit Rate S. F." + ), + prior_near_land_pct_total = list( + style = "pct", display_name = "Lnd. % of Total" + ), + pred_pin_final_fmv = list(style = "price", display_name = "Before Rounding"), + pred_pin_final_fmv_land = list(style = "price", display_name = "Land"), + pred_pin_final_fmv_bldg = list(style = "price", display_name = "Building"), + pred_pin_final_fmv_round = list(style = "price", display_name = "Total"), + land_rate_per_sqft = list( + style = "2digit_price", display_name = "Lnd. Rate Original" + ), + pred_pin_land_rate_effective = list( + style = "2digit_price", display_name = "Lnd. Rate Effective" + ), + pred_pin_bldg_rate_effective = list( + style = "2digit_price", display_name = "Unit Rate S. F." + ), + pred_pin_land_pct_total = list( + style = "pct", display_name = "Lnd. % of Tot." + ), + prior_near_yoy_change_nom = list( + style = "price", display_name = "YoY ∆ $" + ), + prior_near_yoy_change_pct = list( + style = "pct", cond = "color_scale", display_name = "YoY ∆ %" + ), + sale_ratio = list( + formula = TRUE, style = "2digit_num", display_name = "Sale Ratio" + ), + valuations_note = list(display_name = "Valuations Notes"), + sale_recent_1_date = list( + cond = "sale_outlier_1", display_name = "Sale Date 1" + ), + sale_recent_1_price = list( + style = "price", cond = "sale_outlier_1", display_name = "Sale Amount 1" + ), + sale_recent_1_outlier_reason = list( + cond = "sale_outlier_1", + display_name = "Non-Arm's-Length Sale Flag 1" + ), + sale_recent_1_document_num = list( + cond = "sale_outlier_1", display_name = "Sale Doc. 1" + ), + sale_recent_1_num_parcels = list( + cond = "sale_outlier_1", display_name = "Sale Num. Parcels 1" + ), + sale_recent_2_date = list( + cond = "sale_outlier_2", display_name = "Sale Date 2" + ), + sale_recent_2_price = list( + style = "price", cond = "sale_outlier_2", display_name = "Sale Amount 2" + ), + sale_recent_2_outlier_reason = list( + cond = "sale_outlier_2", + display_name = "Non-Arm's-Length Sale Flag 2" + ), + sale_recent_2_document_num = list( + cond = "sale_outlier_2", display_name = "Sale Doc. 2" + ), + sale_recent_2_num_parcels = list( + cond = "sale_outlier_2", display_name = "Sale Num. Parcels 2" + ), + char_yrblt = list(display_name = "Year Built"), + char_total_bldg_sf = list( + style = "comma", display_name = "Total Bld. S. F." + ), + char_land_sf = list(style = "comma", display_name = "Lnd. S. F."), + char_unit_sf = list(style = "comma", display_name = "Condo Unit S. F."), + meta_pin10_bldg_roll_mean = list( + style = "price", display_name = "5-Year Bldg. Sale Price Avg." + ), + meta_pin10_bldg_roll_count = list( + style = "comma", display_name = "5-Year Bldg. Sale Count" + ), + flag_pin10_bldg_roll_mean_imputed = list( + display_name = "Bldg. Sale Avg. was Imputed" + ), + flag_nonlivable_space = list(display_name = "Condo Non-Livable Space"), + flag_proration_sum_not_1 = list( + display_name = "% Ownership Sum Not Equal to 1" + ), + flag_pin_is_multiland = list(display_name = "Multi-Land"), + flag_land_gte_95_percentile = list( + display_name = "Lnd. >= 95% in Town" + ), + flag_land_value_capped = list(display_name = "Land Value Capped"), + flag_prior_near_to_pred_unchanged = list(display_name = "Value Unchanged"), + flag_prior_near_yoy_inc_gt_50_pct = list( + display_name = "YoY Change >= 50%" + ), + flag_prior_near_yoy_dec_gt_5_pct = list( + display_name = "YoY Change <= -5%" + ), + flag_has_recent_assessable_permit = list( + display_name = "Recent Assessable Permit" + ), + total_mv = list( + formula = TRUE, style = "price", hidden = TRUE, display_name = "Total MV" + ), + mv_difference = list( + formula = TRUE, style = "price", hidden = TRUE, + display_name = "MV Difference" + ) +) + +bldg_schema <- list( + meta_pin10 = list(display_name = "PIN10"), + meta_nbhd_code = list(display_name = "Nbhd."), + property_full_address = list(display_name = "Street Address"), + loc_tax_municipality_name = list(display_name = "Municipality"), + num_pin_livable = list(style = "comma", display_name = "Num. Livable PINs"), + num_pin_nonlivable = list( + style = "comma", display_name = "Num. Non-Livable PINs" + ), + total_tieback_proration_rate = list( + style = "pct", display_name = "Total Res. % Ownership" + ), + prior_near_bldg_total = list( + style = "price", display_name = "Building Total FMV" + ), + pred_pin_final_fmv_bldg_total = list( + style = "price", display_name = "Building Total FMV" + ), + prior_near_yoy_change_nom_total = list( + style = "price", display_name = "YoY ∆ $" + ), + prior_near_yoy_change_pct = list( + style = "pct", cond = "color_scale", display_name = "YoY ∆ %" + ), + char_yrblt = list(display_name = "Year Built"), + char_total_bldg_sf = list(style = "comma", display_name = "Bld. Total S. F.") +) + +# Validate that the schemas match the template column headers +validate_schema_vs_template( + pin_detail_schema, + here("misc", "desk_review_template.xlsx"), + "PIN Detail", + header_row = 4 +) +validate_schema_vs_template( + bldg_schema, + here("misc", "desk_review_template.xlsx"), + "Buildings", + header_row = 4 +) + +# Precompute Excel column letters used in formula strings and cond. formatting +fmv_round_col <- int2col(col_pos(pin_detail_schema, "pred_pin_final_fmv_round")) +prior_near_tot_col <- int2col(col_pos(pin_detail_schema, "prior_near_tot")) +sale1_price_col <- int2col(col_pos(pin_detail_schema, "sale_recent_1_price")) +sale1_outlier_col <- int2col( + col_pos(pin_detail_schema, "sale_recent_1_outlier_reason") +) +sale2_outlier_col <- int2col( + col_pos(pin_detail_schema, "sale_recent_2_outlier_reason") +) +sale1_num_parcels_col <- int2col( + col_pos(pin_detail_schema, "sale_recent_1_num_parcels") +) +sale2_num_parcels_col <- int2col( + col_pos(pin_detail_schema, "sale_recent_2_num_parcels") +) + + + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# 4. Prep Desk Review ---------------------------------------------------------- #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - message("Preparing data for Desk Review export") @@ -110,7 +309,6 @@ assessment_pin_prepped <- assessment_pin %>% ~ as.numeric(.x) ), # Empty fields to be filled out via other means - char_type_resd = NA, valuations_note = NA, sale_ratio = NA ) %>% @@ -124,32 +322,12 @@ assessment_pin_prepped <- assessment_pin %>% sale_recent_2_outlier_reason = if_else(sale_recent_2_is_outlier, sale_recent_2_outlier_reason, "") ) %>% - # Select fields for output to workbook + # Select fields for output to workbook using schema order select( - township_code, meta_pin, meta_class, meta_nbhd_code, - property_full_address, loc_tax_municipality_name, meta_pin10, - meta_tieback_key_pin, meta_tieback_proration_rate, - prior_near_land, prior_near_bldg, prior_near_tot, - prior_near_land_rate, prior_near_bldg_rate, prior_near_land_pct_total, - pred_pin_final_fmv, pred_pin_final_fmv_land, pred_pin_final_fmv_bldg, - pred_pin_final_fmv_round, land_rate_per_sqft, pred_pin_land_rate_effective, - pred_pin_bldg_rate_effective, pred_pin_land_pct_total, - prior_near_yoy_change_nom, prior_near_yoy_change_pct, - sale_ratio, valuations_note, - sale_recent_1_date, sale_recent_1_price, - sale_recent_1_outlier_reason, sale_recent_1_document_num, - sale_recent_1_num_parcels, - sale_recent_2_date, sale_recent_2_price, - sale_recent_2_outlier_reason, sale_recent_2_document_num, - sale_recent_2_num_parcels, - char_yrblt, char_total_bldg_sf, char_land_sf, - char_unit_sf, meta_pin10_bldg_roll_mean, meta_pin10_bldg_roll_count, - flag_pin10_bldg_roll_mean_imputed, flag_nonlivable_space, - flag_proration_sum_not_1, - flag_pin_is_multiland, flag_land_gte_95_percentile, - flag_land_value_capped, flag_prior_near_to_pred_unchanged, - flag_prior_near_yoy_inc_gt_50_pct, flag_prior_near_yoy_dec_gt_5_pct, - flag_has_recent_assessable_permit + township_code, + all_of(names(pin_detail_schema)[ + !vapply(pin_detail_schema, function(x) isTRUE(x$hidden), logical(1)) + ]) ) %>% mutate( across(starts_with("flag_"), as.numeric), @@ -206,19 +384,20 @@ assessment_pin10_prepped <- assessment_pin_prepped %>% #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# 4. Export Desk Review -------------------------------------------------------- +# 5. Export Desk Review -------------------------------------------------------- #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Write raw data to sheets for parcel details for (town in unique(assessment_pin_prepped$township_code)) { message("Now processing: ", town_convert(town)) - ## 4.1. PIN-Level ------------------------------------------------------------ + ## 5.1. PIN-Level ------------------------------------------------------------ # Filter building data to specific township assessment_pin10_filtered <- assessment_pin10_prepped %>% filter(township_code == town) %>% - select(-township_code) + select(-township_code) %>% + select(all_of(names(bldg_schema))) building_coords <- assessment_pin10_filtered %>% # Handle a rare edge case where neighborhoods span multiple townships @@ -259,7 +438,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { num_head <- 6 pin_row_range <- (num_head + 1):(nrow(assessment_pin_filtered) + num_head) pin_row_range_w_header <- c(num_head, pin_row_range) - pin_col_range <- 1:54 + pin_col_range <- seq_along(pin_detail_schema) assessment_pin_w_row_ids <- assessment_pin_filtered %>% tibble::rowid_to_column("row_id") %>% @@ -269,8 +448,10 @@ for (town in unique(assessment_pin_prepped$township_code)) { # in the neighborhood breakouts pivot table assessment_pin_mvs <- assessment_pin_w_row_ids %>% mutate( - total_mv = glue::glue("=R{row_id}"), - mv_difference = glue::glue("=(R{row_id}) - (K{row_id})") + total_mv = glue::glue("={fmv_round_col}{row_id}"), + mv_difference = glue::glue( + "=({fmv_round_col}{row_id}) - ({prior_near_tot_col}{row_id})" + ) ) %>% select(total_mv, mv_difference) @@ -279,7 +460,8 @@ for (town in unique(assessment_pin_prepped$township_code)) { assessment_pin_sale_ratios <- assessment_pin_w_row_ids %>% mutate( sale_ratio = glue::glue( - '=IF(ISBLANK(AB{row_id}), "", R{row_id} / AB{row_id})' + '=IF(ISBLANK({sale1_price_col}{row_id}), "",', + ' {fmv_round_col}{row_id} / {sale1_price_col}{row_id})' ) ) @@ -318,50 +500,30 @@ for (town in unique(assessment_pin_prepped$township_code)) { wb <- loadWorkbook(here("misc", "desk_review_template.xlsx")) # Create formatting styles - style_price <- createStyle(numFmt = "$#,##0") - style_2digit_price <- createStyle(numFmt = "$#,##0.00") - style_2digit_num <- createStyle(numFmt = "0.00") - style_pct <- createStyle(numFmt = "PERCENTAGE") - style_comma <- createStyle(numFmt = "COMMA") - style_link <- createStyle(fontColour = "blue", textDecoration = "underline") - - # Add styles to PIN sheet - addStyle( - wb, pin_sheet_name, - style = style_price, - rows = pin_row_range, - cols = c(9:11, 15:18, 23, 28, 33, 41, 53, 54), gridExpand = TRUE - ) - addStyle( - wb, pin_sheet_name, - style = style_2digit_price, - rows = pin_row_range, cols = c(12:13, 19:21), gridExpand = TRUE - ) - addStyle( - wb, pin_sheet_name, - style = style_2digit_num, - rows = pin_row_range, cols = c(25), gridExpand = TRUE - ) - addStyle( - wb, pin_sheet_name, - style = style_pct, - rows = pin_row_range, cols = c(8, 14, 22, 24), gridExpand = TRUE - ) - addStyle( - wb, pin_sheet_name, - style = style_comma, - rows = pin_row_range, cols = c(38, 39, 40, 42), gridExpand = TRUE - ) - addStyle( - wb, pin_sheet_name, - style = style_link, - rows = pin_row_range, cols = c(6), gridExpand = TRUE - ) - addFilter(wb, pin_sheet_name, 6, pin_col_range) + wb_styles <- list( + price = createStyle(numFmt = "$#,##0"), + "2digit_price" = createStyle(numFmt = "$#,##0.00"), + "2digit_num" = createStyle(numFmt = "0.00"), + pct = createStyle(numFmt = "PERCENTAGE"), + comma = createStyle(numFmt = "COMMA"), + link = createStyle(fontColour = "blue", textDecoration = "underline") + ) + + # Add styles to PIN sheet using schema + for (style_name in names(wb_styles)) { + style_cols <- cols_with_style(pin_detail_schema, style_name) + if (length(style_cols) == 0) next + addStyle( + wb, pin_sheet_name, + style = wb_styles[[style_name]], + rows = pin_row_range, cols = style_cols, gridExpand = TRUE + ) + } + addFilter(wb, pin_sheet_name, num_head, pin_col_range) # Format YoY % change column with a range of colors from low to high walk( - c(24), + cols_with_cond(pin_detail_schema, "color_scale"), ~ conditionalFormatting( wb, pin_sheet_name, cols = .x, @@ -374,28 +536,28 @@ for (town in unique(assessment_pin_prepped$township_code)) { # Format sale such that they are orange for adjusted multi-PIN sales conditionalFormatting( wb, pin_sheet_name, - cols = 27:31, + cols = cols_with_cond(pin_detail_schema, "sale_outlier_1"), rows = pin_row_range, style = createStyle(bgFill = "#FFCC99"), - rule = "$AE7=2", + rule = paste0("$", sale1_num_parcels_col, num_head + 1, "=2"), type = "expression" ) conditionalFormatting( wb, pin_sheet_name, - cols = 32:37, + cols = cols_with_cond(pin_detail_schema, "sale_outlier_2"), rows = pin_row_range, style = createStyle(bgFill = "#FFCC99"), - rule = "$AJ7=2", + rule = paste0("$", sale2_num_parcels_col, num_head + 1, "=2"), type = "expression" ) # Format sale columns such that they are red if the sale has an outlier flag conditionalFormatting( wb, pin_sheet_name, - cols = 27:31, + cols = cols_with_cond(pin_detail_schema, "sale_outlier_1"), rows = pin_row_range, style = createStyle(bgFill = "#FF9999"), - rule = '$AC7!=""', + rule = paste0('$', sale1_outlier_col, num_head + 1, '!=""'), type = "expression" ) # For some reason vector cols don't work with expressions, so we have @@ -403,36 +565,37 @@ for (town in unique(assessment_pin_prepped$township_code)) { # to apply it to the second range of columns conditionalFormatting( wb, pin_sheet_name, - cols = 32:37, + cols = cols_with_cond(pin_detail_schema, "sale_outlier_2"), rows = pin_row_range, style = createStyle(bgFill = "#FF9999"), - rule = '$AH7!=""', + rule = paste0('$', sale2_outlier_col, num_head + 1, '!=""'), type = "expression" ) # Write PIN-level data to workbook writeData( wb, pin_sheet_name, assessment_pin_filtered, - startCol = 1, startRow = 7, colNames = FALSE + startCol = 1, startRow = num_head + 1, colNames = FALSE ) # Write formulas and headers to workbook writeFormula( wb, pin_sheet_name, assessment_pin_filtered$meta_pin, - startCol = 1, - startRow = 7 + startCol = col_pos(pin_detail_schema, "meta_pin"), + startRow = num_head + 1 ) writeFormula( wb, pin_sheet_name, assessment_pin_filtered$meta_pin10, - startCol = 6, - startRow = 7 + startCol = col_pos(pin_detail_schema, "meta_pin10"), + startRow = num_head + 1 ) writeFormula( wb, pin_sheet_name, assessment_pin_sale_ratios$sale_ratio, - startCol = 25, startRow = 7 + startCol = col_pos(pin_detail_schema, "sale_ratio"), + startRow = num_head + 1 ) writeData( wb, pin_sheet_name, tibble(sheet_header), @@ -444,31 +607,34 @@ for (town in unique(assessment_pin_prepped$township_code)) { ) writeData( wb, pin_sheet_name, tibble(comp_header), - startCol = 9, startRow = 5, colNames = FALSE + startCol = col_pos(pin_detail_schema, "prior_near_land"), + startRow = num_head - 1, colNames = FALSE ) writeData( wb, pin_sheet_name, tibble(model_header), - startCol = 15, startRow = 5, colNames = FALSE + startCol = col_pos(pin_detail_schema, "pred_pin_final_fmv"), + startRow = num_head - 1, colNames = FALSE ) # Write hidden formulas writeFormula( wb, pin_sheet_name, assessment_pin_mvs$total_mv, - startCol = 53, - startRow = 7 + startCol = col_pos(pin_detail_schema, "total_mv"), + startRow = num_head + 1 ) writeFormula( wb, pin_sheet_name, assessment_pin_mvs$mv_difference, - startCol = 54, - startRow = 7 + startCol = col_pos(pin_detail_schema, "mv_difference"), + startRow = num_head + 1 ) + hidden_cols <- cols_hidden(pin_detail_schema) setColWidths( wb, pin_sheet_name, - c(53, 54), - widths = 1, - hidden = c(TRUE, TRUE), ignoreMergedCells = FALSE + hidden_cols, + widths = rep(1, length(hidden_cols)), + hidden = rep(TRUE, length(hidden_cols)), ignoreMergedCells = FALSE ) # Add a named range for the PIN-level data, which the template will use @@ -479,37 +645,34 @@ for (town in unique(assessment_pin_prepped$township_code)) { name = "pin_detail_range", overwrite = TRUE ) - # 4.2. Building-Level -------------------------------------------------------- + # 5.2. Building-Level -------------------------------------------------------- # Get range of rows in the building data + number of header rows bldg_row_range <- 5:(nrow(assessment_pin10_filtered) + 6) - # Add styles to bldg sheet - addStyle( - wb, bldg_sheet_name, - style = style_price, - rows = bldg_row_range, cols = c(8:10), gridExpand = TRUE - ) - addStyle( - wb, bldg_sheet_name, - style = style_pct, - rows = bldg_row_range, cols = c(7, 11), gridExpand = TRUE - ) - addStyle( - wb, bldg_sheet_name, - style = style_comma, - rows = bldg_row_range, cols = c(5:6, 13), gridExpand = TRUE - ) - addFilter(wb, bldg_sheet_name, 4, 1:13) + # Add styles to bldg sheet using schema + for (style_name in names(wb_styles)) { + style_cols <- cols_with_style(bldg_schema, style_name) + if (length(style_cols) == 0) next + addStyle( + wb, bldg_sheet_name, + style = wb_styles[[style_name]], + rows = bldg_row_range, cols = style_cols, gridExpand = TRUE + ) + } + addFilter(wb, bldg_sheet_name, 4, seq_along(bldg_schema)) # Format YoY % change column with a range of colors from low to high - conditionalFormatting( - wb, bldg_sheet_name, - cols = c(11), - rows = bldg_row_range, - style = c("#F8696B", "#FFFFFF", "#00B0F0"), - rule = c(-1, 0, 1), - type = "colourScale" + walk( + cols_with_cond(bldg_schema, "color_scale"), + ~ conditionalFormatting( + wb, bldg_sheet_name, + cols = .x, + rows = bldg_row_range, + style = c("#F8696B", "#FFFFFF", "#00B0F0"), + rule = c(-1, 0, 1), + type = "colourScale" + ) ) # Write bldg-level data to workbook @@ -521,11 +684,13 @@ for (town in unique(assessment_pin_prepped$township_code)) { # Write formulas and headers to workbook writeData( wb, bldg_sheet_name, tibble(comp_header), - startCol = 8, startRow = 3, colNames = FALSE + startCol = col_pos(bldg_schema, "prior_near_bldg_total"), + startRow = 3, colNames = FALSE ) writeData( wb, bldg_sheet_name, tibble(model_header), - startCol = 9, startRow = 3, colNames = FALSE + startCol = col_pos(bldg_schema, "pred_pin_final_fmv_bldg_total"), + startRow = 3, colNames = FALSE ) # Save workbook to file based on town name @@ -557,7 +722,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# 5. Prep iasWorld Upload ------------------------------------------------------ +# 6. Prep iasWorld Upload ------------------------------------------------------ #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Prepare data for iasWorld upload @@ -611,7 +776,7 @@ upload_data_prepped <- assessment_pin %>% #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -# 6. Export iasWorld Upload ---------------------------------------------------- +# 7. Export iasWorld Upload ---------------------------------------------------- #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Write each town to a headerless CSV for mass upload From 671d03c59b59752da21a421da98d18e3ab3c73ca Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Mon, 17 Aug 2026 19:20:27 +0000 Subject: [PATCH 02/14] update helper --- R/helpers.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/helpers.R b/R/helpers.R index ceb8d3b..f470147 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -323,7 +323,9 @@ create_rolling_origin_splits <- function(data, # Schema helper functions for workbook creation -------------------------------- col_pos <- function(schema, col_name) { - which(names(schema) == col_name) + idx <- which(names(schema) == col_name) + if (length(idx) == 0) stop(glue::glue("Column '{col_name}' not found in schema")) + idx } cols_with_style <- function(schema, style_name) { From e1c8cd2505aae7f6e9d1318a57c6f91b24240917 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Mon, 17 Aug 2026 21:10:05 +0000 Subject: [PATCH 03/14] lintr changes --- pipeline/07-export.R | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 2e99587..04ad756 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -274,7 +274,6 @@ sale2_num_parcels_col <- int2col( ) - #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # 4. Prep Desk Review ---------------------------------------------------------- #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -455,7 +454,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { mutate( sale_ratio = glue::glue( '=IF(ISBLANK({sale1_price_col}{row_id}), "",', - ' {fmv_round_col}{row_id} / {sale1_price_col}{row_id})' + " {fmv_round_col}{row_id} / {sale1_price_col}{row_id})" ) ) @@ -551,7 +550,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { cols = cols_with_cond(pin_detail_schema, "sale_outlier_1"), rows = pin_row_range, style = createStyle(bgFill = "#FF9999"), - rule = paste0('$', sale1_outlier_col, num_head + 1, '!=""'), + rule = paste0("$", sale1_outlier_col, num_head + 1, '!=""'), type = "expression" ) # For some reason vector cols don't work with expressions, so we have @@ -562,7 +561,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { cols = cols_with_cond(pin_detail_schema, "sale_outlier_2"), rows = pin_row_range, style = createStyle(bgFill = "#FF9999"), - rule = paste0('$', sale2_outlier_col, num_head + 1, '!=""'), + rule = paste0("$", sale2_outlier_col, num_head + 1, '!=""'), type = "expression" ) From 25fca04c38795009247d4cba836c998a4a3aaaf9 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Mon, 17 Aug 2026 22:05:35 +0000 Subject: [PATCH 04/14] lintr --- R/helpers.R | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/R/helpers.R b/R/helpers.R index f470147..7381579 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -324,7 +324,11 @@ create_rolling_origin_splits <- function(data, col_pos <- function(schema, col_name) { idx <- which(names(schema) == col_name) - if (length(idx) == 0) stop(glue::glue("Column '{col_name}' not found in schema")) + if (length(idx) == 0) { + stop(glue::glue( + "Column '{col_name}' not found in schema" + )) + } idx } From 593b4cd2909f5b2d72ef312c2f7f0105af4f07b4 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 19 Aug 2026 18:59:21 +0000 Subject: [PATCH 05/14] Jean edtis --- R/helpers.R | 20 +++++--------------- pipeline/07-export.R | 21 ++++++++++++++++----- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/R/helpers.R b/R/helpers.R index 7381579..34d43d6 100644 --- a/R/helpers.R +++ b/R/helpers.R @@ -322,28 +322,18 @@ create_rolling_origin_splits <- function(data, # Schema helper functions for workbook creation -------------------------------- +# Find a column's 1-based position in a schema list by name col_pos <- function(schema, col_name) { - idx <- which(names(schema) == col_name) - if (length(idx) == 0) { - stop(glue::glue( - "Column '{col_name}' not found in schema" - )) - } - idx + pos <- which(names(schema) == col_name) + if (length(pos) == 0L) stop("Column '", col_name, "' not found in schema") + pos } +# Indices of all schema columns whose `style` field equals `style_name` cols_with_style <- function(schema, style_name) { which(vapply(schema, function(x) identical(x$style, style_name), logical(1))) } -cols_with_cond <- function(schema, cond_name) { - which(vapply(schema, function(x) identical(x$cond, cond_name), logical(1))) -} - -cols_hidden <- function(schema) { - which(vapply(schema, function(x) isTRUE(x$hidden), logical(1))) -} - validate_schema_vs_template <- function(schema, template_path, sheet_name, header_row) { normalize <- function(x) gsub("\\s+", " ", trimws(x)) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 04ad756..3fb9432 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -380,6 +380,16 @@ assessment_pin10_prepped <- assessment_pin_prepped %>% # 5. Export Desk Review -------------------------------------------------------- #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# Indices of all schema columns whose `cond` field equals `cond_name` +cols_with_cond <- function(schema, cond_name) { + which(vapply(schema, function(x) identical(x$cond, cond_name), logical(1))) +} + +# Indices of all schema columns marked hidden = TRUE +cols_hidden <- function(schema) { + which(vapply(schema, function(x) isTRUE(x$hidden), logical(1))) +} + # Write raw data to sheets for parcel details for (town in unique(assessment_pin_prepped$township_code)) { message("Now processing: ", town_convert(town)) @@ -641,7 +651,8 @@ for (town in unique(assessment_pin_prepped$township_code)) { # 5.2. Building-Level -------------------------------------------------------- # Get range of rows in the building data + number of header rows - bldg_row_range <- 5:(nrow(assessment_pin10_filtered) + 6) + num_head_bldg <- 4 + bldg_row_range <- (num_head_bldg + 1):(nrow(assessment_pin10_filtered) + num_head_bldg) # Add styles to bldg sheet using schema for (style_name in names(wb_styles)) { @@ -653,7 +664,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { rows = bldg_row_range, cols = style_cols, gridExpand = TRUE ) } - addFilter(wb, bldg_sheet_name, 4, seq_along(bldg_schema)) + addFilter(wb, bldg_sheet_name, num_head_bldg, seq_along(bldg_schema)) # Format YoY % change column with a range of colors from low to high walk( @@ -671,19 +682,19 @@ for (town in unique(assessment_pin_prepped$township_code)) { # Write bldg-level data to workbook writeData( wb, bldg_sheet_name, assessment_pin10_filtered, - startCol = 1, startRow = 5, colNames = FALSE + startCol = 1, startRow = num_head_bldg + 1, colNames = FALSE ) # Write formulas and headers to workbook writeData( wb, bldg_sheet_name, tibble(comp_header), startCol = col_pos(bldg_schema, "prior_near_bldg_total"), - startRow = 3, colNames = FALSE + startRow = num_head_bldg - 1, colNames = FALSE ) writeData( wb, bldg_sheet_name, tibble(model_header), startCol = col_pos(bldg_schema, "pred_pin_final_fmv_bldg_total"), - startRow = 3, colNames = FALSE + startRow = num_head_bldg - 1, colNames = FALSE ) # Save workbook to file based on town name From a8ea312385bb0dc97c6951acdc1996c2eb9a61d7 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 2 Sep 2026 16:23:38 +0000 Subject: [PATCH 06/14] update to select based on schema --- pipeline/07-export.R | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 3fb9432..33cb00b 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -305,7 +305,9 @@ assessment_pin_prepped <- assessment_pin %>% ), # Empty fields to be filled out via other means valuations_note = NA, - sale_ratio = NA + sale_ratio = NA, + total_mv = NA, + mv_difference = NA ) %>% # Add assessable permit flag left_join(flag_assessable_permits, by = c("meta_pin" = "pin")) %>% @@ -317,13 +319,8 @@ assessment_pin_prepped <- assessment_pin %>% sale_recent_2_outlier_reason = if_else(sale_recent_2_is_outlier, sale_recent_2_outlier_reason, "") ) %>% - # Select fields for output to workbook using schema order - select( - township_code, - all_of(names(pin_detail_schema)[ - !vapply(pin_detail_schema, function(x) isTRUE(x$hidden), logical(1)) - ]) - ) %>% + # This select statement aligns the pin dataframe with the pin detail schema + select(township_code, all_of(names(pin_detail_schema))) %>% mutate( across(starts_with("flag_"), as.numeric), across(where(is.numeric), ~ na_if(.x, Inf)) @@ -435,7 +432,8 @@ for (town in unique(assessment_pin_prepped$township_code)) { ) ) ) %>% - select(-building_coord) + # This select statement aligns the pin dataframe with the pin detail schema + select(all_of(names(pin_detail_schema))) # Get range of rows in the PIN data + number of header rows num_head <- 6 From 7f87bda7fd06b6df8c88b0a5fa0d5c46695215ed Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 2 Sep 2026 17:59:32 +0000 Subject: [PATCH 07/14] explicetely remove mv columns --- pipeline/07-export.R | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 33cb00b..7bf1a62 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -305,9 +305,7 @@ assessment_pin_prepped <- assessment_pin %>% ), # Empty fields to be filled out via other means valuations_note = NA, - sale_ratio = NA, - total_mv = NA, - mv_difference = NA + sale_ratio = NA ) %>% # Add assessable permit flag left_join(flag_assessable_permits, by = c("meta_pin" = "pin")) %>% @@ -319,8 +317,6 @@ assessment_pin_prepped <- assessment_pin %>% sale_recent_2_outlier_reason = if_else(sale_recent_2_is_outlier, sale_recent_2_outlier_reason, "") ) %>% - # This select statement aligns the pin dataframe with the pin detail schema - select(township_code, all_of(names(pin_detail_schema))) %>% mutate( across(starts_with("flag_"), as.numeric), across(where(is.numeric), ~ na_if(.x, Inf)) @@ -433,7 +429,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { ) ) %>% # This select statement aligns the pin dataframe with the pin detail schema - select(all_of(names(pin_detail_schema))) + select(all_of(setdiff(names(pin_detail_schema), c("total_mv", "mv_difference")))) # Get range of rows in the PIN data + number of header rows num_head <- 6 From 2793a7fc577c316ea408171a040cd7d76030f6cd Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 2 Sep 2026 18:04:37 +0000 Subject: [PATCH 08/14] lintr --- pipeline/07-export.R | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 7bf1a62..82bf5b3 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -429,7 +429,10 @@ for (town in unique(assessment_pin_prepped$township_code)) { ) ) %>% # This select statement aligns the pin dataframe with the pin detail schema - select(all_of(setdiff(names(pin_detail_schema), c("total_mv", "mv_difference")))) + select(all_of(setdiff( + names(pin_detail_schema), + c("total_mv", "mv_difference") + ))) # Get range of rows in the PIN data + number of header rows num_head <- 6 @@ -646,7 +649,8 @@ for (town in unique(assessment_pin_prepped$township_code)) { # Get range of rows in the building data + number of header rows num_head_bldg <- 4 - bldg_row_range <- (num_head_bldg + 1):(nrow(assessment_pin10_filtered) + num_head_bldg) + bldg_row_range <- (num_head_bldg + 1): + (nrow(assessment_pin10_filtered) + num_head_bldg) # Add styles to bldg sheet using schema for (style_name in names(wb_styles)) { From 7c0646661647abf768dcdc51ad656b58c8672281 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 2 Sep 2026 18:09:23 +0000 Subject: [PATCH 09/14] standardize to same as model --- pipeline/07-export.R | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 82bf5b3..5dfe3db 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -428,11 +428,10 @@ for (town in unique(assessment_pin_prepped$township_code)) { ) ) ) %>% - # This select statement aligns the pin dataframe with the pin detail schema - select(all_of(setdiff( - names(pin_detail_schema), - c("total_mv", "mv_difference") - ))) + # This select statement aligns the pin dataframe with the pin schema + # total_mv and mv_difference are written separately + # via writeFormula below to populate the pivot table + select(all_of(names(pin_detail_schema)), -total_mv, -mv_difference) # Get range of rows in the PIN data + number of header rows num_head <- 6 From 13046790a20719f0e6ac1d5b3e404b7df08a3b9d Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 2 Sep 2026 18:15:07 +0000 Subject: [PATCH 10/14] revert to set diff --- pipeline/07-export.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 5dfe3db..ad17207 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -431,7 +431,10 @@ for (town in unique(assessment_pin_prepped$township_code)) { # This select statement aligns the pin dataframe with the pin schema # total_mv and mv_difference are written separately # via writeFormula below to populate the pivot table - select(all_of(names(pin_detail_schema)), -total_mv, -mv_difference) + select(all_of(setdiff( + names(pin_detail_schema), + c("total_mv", "mv_difference") + ))) # Get range of rows in the PIN data + number of header rows num_head <- 6 From 3e4d1e0ef76a91f5e54af99cac3c90e9d840af8c Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Wed, 2 Sep 2026 20:23:44 +0000 Subject: [PATCH 11/14] add nolint --- pipeline/07-export.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index ad17207..e99a832 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -652,7 +652,7 @@ for (town in unique(assessment_pin_prepped$township_code)) { # Get range of rows in the building data + number of header rows num_head_bldg <- 4 bldg_row_range <- (num_head_bldg + 1): - (nrow(assessment_pin10_filtered) + num_head_bldg) + (nrow(assessment_pin10_filtered) + num_head_bldg) # nolint # Add styles to bldg sheet using schema for (style_name in names(wb_styles)) { From 9a1e9bf11cc937ebb8b7d3a2188788e0eb4a1434 Mon Sep 17 00:00:00 2001 From: Damonamajor <56321109+Damonamajor@users.noreply.github.com> Date: Fri, 4 Sep 2026 10:06:00 -0500 Subject: [PATCH 12/14] Update pipeline/07-export.R Co-authored-by: Jean Cochrane --- pipeline/07-export.R | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index e99a832..ba32f9e 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -651,8 +651,10 @@ for (town in unique(assessment_pin_prepped$township_code)) { # Get range of rows in the building data + number of header rows num_head_bldg <- 4 - bldg_row_range <- (num_head_bldg + 1): - (nrow(assessment_pin10_filtered) + num_head_bldg) # nolint + bldg_row_range <- seq( + num_head_bldg + 1, + nrow(assessment_pin10_filtered) + num_head_bldg + ) # Add styles to bldg sheet using schema for (style_name in names(wb_styles)) { From 91d01dd611add5ed09562f74e11ef6a43da5d3e3 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Fri, 4 Sep 2026 15:51:01 +0000 Subject: [PATCH 13/14] add commenting for desk review and iasworld upload --- pipeline/07-export.R | 78 +++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index ba32f9e..7277e8c 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -260,18 +260,6 @@ validate_schema_vs_template( fmv_round_col <- int2col(col_pos(pin_detail_schema, "pred_pin_final_fmv_round")) prior_near_tot_col <- int2col(col_pos(pin_detail_schema, "prior_near_tot")) sale1_price_col <- int2col(col_pos(pin_detail_schema, "sale_recent_1_price")) -sale1_outlier_col <- int2col( - col_pos(pin_detail_schema, "sale_recent_1_outlier_reason") -) -sale2_outlier_col <- int2col( - col_pos(pin_detail_schema, "sale_recent_2_outlier_reason") -) -sale1_num_parcels_col <- int2col( - col_pos(pin_detail_schema, "sale_recent_1_num_parcels") -) -sale2_num_parcels_col <- int2col( - col_pos(pin_detail_schema, "sale_recent_2_num_parcels") -) #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -385,7 +373,7 @@ cols_hidden <- function(schema) { # Write raw data to sheets for parcel details for (town in unique(assessment_pin_prepped$township_code)) { - message("Now processing: ", town_convert(town)) + message("Now processing desk review: ", town_convert(town)) ## 5.1. PIN-Level ------------------------------------------------------------ @@ -535,44 +523,30 @@ for (town in unique(assessment_pin_prepped$township_code)) { type = "colourScale" ) ) - # Format sale such that they are orange for adjusted multi-PIN sales - conditionalFormatting( - wb, pin_sheet_name, - cols = cols_with_cond(pin_detail_schema, "sale_outlier_1"), - rows = pin_row_range, - style = createStyle(bgFill = "#FFCC99"), - rule = paste0("$", sale1_num_parcels_col, num_head + 1, "=2"), - type = "expression" - ) - conditionalFormatting( - wb, pin_sheet_name, - cols = cols_with_cond(pin_detail_schema, "sale_outlier_2"), - rows = pin_row_range, - style = createStyle(bgFill = "#FFCC99"), - rule = paste0("$", sale2_num_parcels_col, num_head + 1, "=2"), - type = "expression" - ) - - # Format sale columns such that they are red if the sale has an outlier flag - conditionalFormatting( - wb, pin_sheet_name, - cols = cols_with_cond(pin_detail_schema, "sale_outlier_1"), - rows = pin_row_range, - style = createStyle(bgFill = "#FF9999"), - rule = paste0("$", sale1_outlier_col, num_head + 1, '!=""'), - type = "expression" - ) - # For some reason vector cols don't work with expressions, so we have - # to duplicate the conditional formatting for the sale outlier flag above - # to apply it to the second range of columns - conditionalFormatting( - wb, pin_sheet_name, - cols = cols_with_cond(pin_detail_schema, "sale_outlier_2"), - rows = pin_row_range, - style = createStyle(bgFill = "#FF9999"), - rule = paste0("$", sale2_outlier_col, num_head + 1, '!=""'), - type = "expression" - ) + # Format sale columns: orange for multi-PIN sales, red when outlier flag is present + for (sale_num in 1:2) { + sale_cond <- paste0("sale_outlier_", sale_num) + num_parcels_col <- int2col(col_pos( + pin_detail_schema, paste0("sale_recent_", sale_num, "_num_parcels") + )) + outlier_col <- int2col(col_pos( + pin_detail_schema, paste0("sale_recent_", sale_num, "_outlier_reason") + )) + conditionalFormatting(wb, pin_sheet_name, + cols = cols_with_cond(pin_detail_schema, sale_cond), + rows = pin_row_range, + style = createStyle(bgFill = "#FFCC99"), + rule = paste0("$", num_parcels_col, num_head + 1, "=2"), + type = "expression" + ) + conditionalFormatting(wb, pin_sheet_name, + cols = cols_with_cond(pin_detail_schema, sale_cond), + rows = pin_row_range, + style = createStyle(bgFill = "#FF9999"), + rule = paste0("$", outlier_col, num_head + 1, '!=""'), + type = "expression" + ) + } # Write PIN-level data to workbook writeData( @@ -783,7 +757,7 @@ upload_data_prepped <- assessment_pin %>% # Write each town to a headerless CSV for mass upload for (town in unique(upload_data_prepped$township_code)) { - message("Now processing: ", town_convert(town)) + message("Now processing iasWorld upload: ", town_convert(town)) upload_data_fil <- upload_data_prepped %>% filter(township_code == town, MV > 0, !is.na(MV)) %>% From 739242379bc8a46587fde806c4d3aa5d6bd3c393 Mon Sep 17 00:00:00 2001 From: Damonamajor Date: Fri, 4 Sep 2026 16:04:34 +0000 Subject: [PATCH 14/14] lintr --- pipeline/07-export.R | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pipeline/07-export.R b/pipeline/07-export.R index 7277e8c..c9838af 100644 --- a/pipeline/07-export.R +++ b/pipeline/07-export.R @@ -373,7 +373,7 @@ cols_hidden <- function(schema) { # Write raw data to sheets for parcel details for (town in unique(assessment_pin_prepped$township_code)) { - message("Now processing desk review: ", town_convert(town)) + message("Now processing: ", town_convert(town)) ## 5.1. PIN-Level ------------------------------------------------------------ @@ -523,7 +523,8 @@ for (town in unique(assessment_pin_prepped$township_code)) { type = "colourScale" ) ) - # Format sale columns: orange for multi-PIN sales, red when outlier flag is present + # Format sale columns: orange for multi-PIN sales + # red when outlier flag is present for (sale_num in 1:2) { sale_cond <- paste0("sale_outlier_", sale_num) num_parcels_col <- int2col(col_pos( @@ -755,9 +756,11 @@ upload_data_prepped <- assessment_pin %>% # 7. Export iasWorld Upload ---------------------------------------------------- #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +message("Preparing data for iasWorld export") + # Write each town to a headerless CSV for mass upload for (town in unique(upload_data_prepped$township_code)) { - message("Now processing iasWorld upload: ", town_convert(town)) + message("Now processing: ", town_convert(town)) upload_data_fil <- upload_data_prepped %>% filter(township_code == town, MV > 0, !is.na(MV)) %>%