# ----------------------------------------------------------------------
# I. DATA & LIBRARY LOADING
# ----------------------------------------------------------------------

setwd("~/Desktop/Statistical Model/assignment_3")

library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ readr     2.1.5
✔ ggplot2   3.5.2     ✔ stringr   1.5.1
✔ lubridate 1.9.3     ✔ tibble    3.2.1
✔ purrr     1.2.0     ✔ tidyr     1.3.1── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
✖ MASS::select()  masks dplyr::select()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(lubridate)
library(scales)

Attaching package: ‘scales’

The following object is masked from ‘package:purrr’:

    discard

The following object is masked from ‘package:readr’:

    col_factor
library(dplyr)
library(ggplot2)

# Load Data
raw_real_estate_2016 <- read.csv("2016_brooklyn.csv")
raw_real_estate_2017 <- read.csv("2017_brooklyn.csv")
raw_real_estate_2018 <- read.csv("2018_brooklyn.csv")
raw_real_estate_2019 <- read.csv("2019_brooklyn.csv")
raw_real_estate_2020 <- read.csv("2020_brooklyn.csv")

# Check Column Names
colnames(raw_real_estate_2016)
 [1] "BROOKLYN.ANNUALIZE.SALE.FOR.2016....All.Sales.From..January.1..2016...December.31..2016."
 [2] "X"                                                                                       
 [3] "X.1"                                                                                     
 [4] "X.2"                                                                                     
 [5] "X.3"                                                                                     
 [6] "X.4"                                                                                     
 [7] "X.5"                                                                                     
 [8] "X.6"                                                                                     
 [9] "X.7"                                                                                     
[10] "X.8"                                                                                     
[11] "X.9"                                                                                     
[12] "X.10"                                                                                    
[13] "X.11"                                                                                    
[14] "X.12"                                                                                    
[15] "X.13"                                                                                    
[16] "X.14"                                                                                    
[17] "X.15"                                                                                    
[18] "X.16"                                                                                    
[19] "X.17"                                                                                    
[20] "X.18"                                                                                    
[21] "X.19"                                                                                    
colnames(raw_real_estate_2017)
 [1] "BROOKLYN.ANNUALIZE.SALE.FOR.2017....All.Sales.From..January.1..2017...December.31..2017."
 [2] "X"                                                                                       
 [3] "X.1"                                                                                     
 [4] "X.2"                                                                                     
 [5] "X.3"                                                                                     
 [6] "X.4"                                                                                     
 [7] "X.5"                                                                                     
 [8] "X.6"                                                                                     
 [9] "X.7"                                                                                     
[10] "X.8"                                                                                     
[11] "X.9"                                                                                     
[12] "X.10"                                                                                    
[13] "X.11"                                                                                    
[14] "X.12"                                                                                    
[15] "X.13"                                                                                    
[16] "X.14"                                                                                    
[17] "X.15"                                                                                    
[18] "X.16"                                                                                    
[19] "X.17"                                                                                    
[20] "X.18"                                                                                    
[21] "X.19"                                                                                    
colnames(raw_real_estate_2018)
 [1] "BROOKLYN.ANNUALIZE.SALE.FOR.2018....All.Sales.From..January.1..2018...December.31..2018."
 [2] "X"                                                                                       
 [3] "X.1"                                                                                     
 [4] "X.2"                                                                                     
 [5] "X.3"                                                                                     
 [6] "X.4"                                                                                     
 [7] "X.5"                                                                                     
 [8] "X.6"                                                                                     
 [9] "X.7"                                                                                     
[10] "X.8"                                                                                     
[11] "X.9"                                                                                     
[12] "X.10"                                                                                    
[13] "X.11"                                                                                    
[14] "X.12"                                                                                    
[15] "X.13"                                                                                    
[16] "X.14"                                                                                    
[17] "X.15"                                                                                    
[18] "X.16"                                                                                    
[19] "X.17"                                                                                    
[20] "X.18"                                                                                    
[21] "X.19"                                                                                    
colnames(raw_real_estate_2019)
 [1] "BROOKLYN.ANNUAL.SALES.FOR.CALENDAR.YEAR.2019...All.Sales.From.January.2019...December.2019..PTS.Sales.as.of.04.01.2020."
 [2] "X"                                                                                                                      
 [3] "X.1"                                                                                                                    
 [4] "X.2"                                                                                                                    
 [5] "X.3"                                                                                                                    
 [6] "X.4"                                                                                                                    
 [7] "X.5"                                                                                                                    
 [8] "X.6"                                                                                                                    
 [9] "X.7"                                                                                                                    
[10] "X.8"                                                                                                                    
[11] "X.9"                                                                                                                    
[12] "X.10"                                                                                                                   
[13] "X.11"                                                                                                                   
[14] "X.12"                                                                                                                   
[15] "X.13"                                                                                                                   
[16] "X.14"                                                                                                                   
[17] "X.15"                                                                                                                   
[18] "X.16"                                                                                                                   
[19] "X.17"                                                                                                                   
[20] "X.18"                                                                                                                   
[21] "X.19"                                                                                                                   
colnames(raw_real_estate_2020)
 [1] "BROOKLYN.ANNUAL.SALES.FOR.CALENDAR.YEAR.2020" "X"                                           
 [3] "X.1"                                          "X.2"                                         
 [5] "X.3"                                          "X.4"                                         
 [7] "X.5"                                          "X.6"                                         
 [9] "X.7"                                          "X.8"                                         
[11] "X.9"                                          "X.10"                                        
[13] "X.11"                                         "X.12"                                        
[15] "X.13"                                         "X.14"                                        
[17] "X.15"                                         "X.16"                                        
[19] "X.17"                                         "X.18"                                        
[21] "X.19"                                        
# Check Raw Data
head(raw_real_estate_2016)
head(raw_real_estate_2017)
head(raw_real_estate_2018)
head(raw_real_estate_2019)
head(raw_real_estate_2020)

# ----------------------------------------------------------------------
# II. CLEAN & COMBINE DATA
# ----------------------------------------------------------------------

# Remove Metadata Rows
real_estate_2016 <- raw_real_estate_2016[-(1:4), ]
real_estate_2017 <- raw_real_estate_2017[-(1:4), ]
real_estate_2018 <- raw_real_estate_2018[-(1:4), ]
real_estate_2019 <- raw_real_estate_2019[-(1:4), ]
real_estate_2020 <- raw_real_estate_2020[-(1:7), ]

head(real_estate_2016)
head(real_estate_2017)
head(real_estate_2018)
head(real_estate_2019)
head(real_estate_2020)

# Standardize Column Names
standardized_column_names <- c("BOROUGH", 
                           "NEIGHBORHOOD",
                           "BUILDING CLASS CATEGORY", 
                           "TAX CLASS AT PRESENT", 
                           "BLOCK", 
                           "LOT",
                           "EASE-MENT",
                           "BUILDING CLASS AT PRESENT",
                           "ADDRESS",
                           "APARTMENT NUMBER",
                           "ZIP CODE",
                           "RESIDENTIAL UNITS",
                           "COMMERCIAL UNITS",
                           "TOTAL UNITS",
                           "LAND SQUARE FEET",
                           "GROSS SQUARE FEET",
                           "YEAR BUILT",
                           "TAX CLASS AT TIME OF SALE",
                           "BUILDING CLASS AT TIME OF SALE",
                           "SALE PRICE",
                           "SALE DATE")

colnames(real_estate_2016) <- standardized_column_names
colnames(real_estate_2017) <- standardized_column_names
colnames(real_estate_2018) <- standardized_column_names
colnames(real_estate_2019) <- standardized_column_names
colnames(real_estate_2020) <- standardized_column_names

head(real_estate_2016)
head(real_estate_2017)
head(real_estate_2018)
head(real_estate_2019)
head(real_estate_2020)

# Set Column Data Types
set_column_types <- function(df) {
  string_cols <- c(
    "BOROUGH", "NEIGHBORHOOD", "BUILDING CLASS CATEGORY", 
    "TAX CLASS AT PRESENT", "BLOCK", "EASE-MENT", 
    "BUILDING CLASS AT PRESENT", "ADDRESS", "APARTMENT NUMBER", 
    "ZIP CODE", "TAX CLASS AT TIME OF SALE", 
    "BUILDING CLASS AT TIME OF SALE"
  )
  
  numeric_cols <- c(
    "RESIDENTIAL UNITS", "COMMERCIAL UNITS", "TOTAL UNITS",
    "LAND SQUARE FEET", "GROSS SQUARE FEET", "YEAR BUILT", "SALE PRICE"
  )
  
  # Clean and convert string/numeric columns first
  df <- df %>%
    mutate(across(where(is.character), str_squish)) %>%
    mutate(across(all_of(string_cols), as.character)) %>%
    mutate(across(all_of(numeric_cols),
                  ~ suppressWarnings(as.numeric(gsub("[^0-9.]", "", str_squish(.))))))
  
  # Handle SALE DATE format flexibly (2-digit vs 4-digit year)
  if (any(grepl("^\\d{1,2}/\\d{1,2}/\\d{2}$", df$`SALE DATE`))) {
    df$`SALE DATE` <- as.Date(df$`SALE DATE`, format = "%m/%d/%y")  # ✅ 2-digit year
  } else {
    df$`SALE DATE` <- as.Date(df$`SALE DATE`, format = "%m/%d/%Y")  # ✅ 4-digit year
  }
  
  return(df)
}




real_estate_2016 <- set_column_types(real_estate_2016)
real_estate_2017 <- set_column_types(real_estate_2017)
real_estate_2018 <- set_column_types(real_estate_2018)
real_estate_2019 <- set_column_types(real_estate_2019)
real_estate_2020 <- set_column_types(real_estate_2020)

head(real_estate_2016)
head(real_estate_2017)
head(real_estate_2018)
head(real_estate_2019)
head(real_estate_2020)

# Filter data 
# first letter of BUILDING CLASS AT TIME OF SALE in c ("A", "R)
# AND number of TOTAL UNITS == 1
# AND number of RESIDENTIAL UNITS == 1
# AND GROSS SQUARE FOOT > 0
# AND SALE PRICE is not N/A

filter_data <- function(df) {
  df %>%
    filter(
      substr(`BUILDING CLASS AT TIME OF SALE`, 1, 1) %in% c("A", "R"),
      `TOTAL UNITS` == 1,
      `RESIDENTIAL UNITS` == 1,
      `GROSS SQUARE FEET` > 0,
      !is.na(`SALE PRICE`)
    )
}

real_estate_2016 <- filter_data(real_estate_2016)
real_estate_2017 <- filter_data(real_estate_2017)
real_estate_2018 <- filter_data(real_estate_2018)
real_estate_2019 <- filter_data(real_estate_2019)
real_estate_2020 <- filter_data(real_estate_2020)

head(real_estate_2016)
head(real_estate_2017)
head(real_estate_2018)
head(real_estate_2019)
head(real_estate_2020)

# Create New "Age" column
datasets <- list(
  real_estate_2016 = real_estate_2016,
  real_estate_2017 = real_estate_2017,
  real_estate_2018 = real_estate_2018,
  real_estate_2019 = real_estate_2019,
  real_estate_2020 = real_estate_2020
)

for (year in 2016:2020) {
  df_name <- paste0("real_estate_", year)
  df <- get(df_name)
  
  df$`SALE DATE` <- as.Date(df$`SALE DATE`)
  df$`SALE YEAR` <- as.numeric(format(df$`SALE DATE`, "%Y"))
  
  df$`BUILDING AGE AT SALE` <- ifelse(
    is.na(df$`YEAR BUILT`) | df$`YEAR BUILT` == 0,
    NA,
    df$`SALE YEAR` - df$`YEAR BUILT`
  )
  assign(df_name, df)
}


# Combine All Data
real_estate_df <- rbind(real_estate_2016, 
                        real_estate_2017, 
                        real_estate_2018, 
                        real_estate_2019,
                        real_estate_2020)

head(real_estate_df)

# Drop Irrelevant columns
real_estate_df <- real_estate_df[ , !(names(real_estate_2016) %in% c(
  "BOROUGH", 
  "EASE-MENT", 
  "APARTMENT NUMBER",
  "TOTAL UNITS", # Always 1
  "RESIDENTIAL UNITS", # Always 1
  "COMMERCIAL UNITS", 
  "ADDRESS NUMBER",
  "SALE YEAR"
))]

# Check Row 
head(real_estate_df)
nrow(real_estate_df)
[1] 19640
## Build cleaned data ready for one-line regressions
## Input: real_estate_df

# 1) Copy and make names R-safe (handles spaces)
df <- real_estate_df
names(df) <- make.names(names(df))

# 2) Identify the price column robustly and coerce to numeric if needed
price_col <- if ("SALE.PRICE" %in% names(df)) "SALE.PRICE" else {
  cand <- grep("^SALE[._ ]?PRICE$", names(df), ignore.case = TRUE, value = TRUE)
  stopifnot(length(cand) > 0); cand[1]
}
if (is.character(df[[price_col]])) {
  df[[price_col]] <- as.numeric(gsub("[^0-9.]", "", df[[price_col]]))
}

# 3) Drop obvious IDs/dates (optional but typical)
drop_cols <- intersect(c("ADDRESS","BLOCK","LOT","SALE.DATE"), names(df))
if (length(drop_cols)) df[drop_cols] <- list(NULL)

# 4) Convert ALL character cols to factors (so lm() makes dummies)
char_cols <- names(df)[sapply(df, is.character)]
if (length(char_cols)) df[char_cols] <- lapply(df[char_cols], as.factor)

# 5) Drop single-level columns (avoids contrasts errors)
one_level <- names(df)[sapply(df, function(x) length(unique(na.omit(x))) <= 1)]
if (length(one_level)) df[one_level] <- list(NULL)

# 6) Keep rows with finite prices; also make a log-ready version (>0)
df_ready     <- subset(df, is.finite(df[[price_col]]))
df_ready_log <- subset(df_ready, df_ready[[price_col]] > 0)

cat("df_ready:", nrow(df_ready), "rows,", ncol(df_ready), "cols\n")
df_ready: 19640 rows, 12 cols
cat("df_ready_log:", nrow(df_ready_log), "rows\n")
df_ready_log: 13985 rows
# ============ LAST-MILE++ (≤40 df, ≥13k rows): ZIP-relative size, timing×neigh, age hinge ============
suppressPackageStartupMessages({ library(dplyr); library(splines); library(MASS) })

wins  <- function(x, qlo=.01, qhi=.99){ q <- quantile(x,c(qlo,qhi),na.rm=TRUE); pmin(q[2],pmax(q[1],x)) }
rmse  <- function(a,b) sqrt(mean((a-b)^2,na.rm=TRUE))
mae   <- function(a,b) mean(abs(a-b),na.rm=TRUE)
count_df <- function(f,d) ncol(model.matrix(f,d))-1L

# ---- data & core numerics ----
df <- real_estate_df
names(df) <- make.names(names(df))
if (is.character(df$SALE.PRICE)) df$SALE.PRICE <- as.numeric(gsub("[^0-9.]","",df$SALE.PRICE))
df <- df %>% filter(is.finite(SALE.PRICE), SALE.PRICE>0)

df <- df %>%
  mutate(
    GSF = pmax(1, suppressWarnings(as.numeric(GROSS.SQUARE.FEET))),
    LSF = pmax(1, suppressWarnings(as.numeric(LAND.SQUARE.FEET))),
    LOG_GSF = log(GSF), LOG_LSF = log(LSF),
    AGE = dplyr::case_when(
      !is.na(BUILDING.AGE.AT.SALE) ~ suppressWarnings(as.numeric(BUILDING.AGE.AT.SALE)),
      !is.na(YEAR.BUILT) ~ as.numeric(format(Sys.Date(),"%Y")) - suppressWarnings(as.numeric(YEAR.BUILT)),
      TRUE ~ NA_real_
    ),
    AGE_LOG = log1p(pmax(0, AGE)),
    FAR = pmin(5, pmax(0.05, GSF / pmax(1, LSF))),
    SALE.DATE = suppressWarnings(as.Date(if ("SALE.DATE" %in% names(df)) SALE.DATE else NA)),
    TIME_INDEX = as.numeric(difftime(SALE.DATE, min(SALE.DATE, na.rm=TRUE), units="days"))/365,
    ZIP = if ("ZIP.CODE" %in% names(df)) as.character(ZIP.CODE) else NA_character_
  ) %>%
  mutate(across(c(SALE.PRICE,GSF,LSF,FAR), wins))

# ---- simple target encodings (in-bag shrinked; we already validated oof earlier) ----
ylog <- log(df$SALE.PRICE)
target_enc <- function(k,y,m=70){
  mu<-mean(y,na.rm=TRUE); key<-as.character(k)
  t<-tapply(y,key,function(v){n<-sum(is.finite(v));(sum(v,na.rm=TRUE)+m*mu)/(pmax(1,n)+m)})
  out<-as.numeric(t[key]); out[!is.finite(out)]<-mu; out
}
df$NEIGH_TE  <- if ("NEIGHBORHOOD"%in%names(df)) target_enc(df$NEIGHBORHOOD,ylog,70) else NA
df$BCLASS_TE <- if ("BUILDING.CLASS.AT.PRESENT"%in%names(df)) target_enc(substr(df$BUILDING.CLASS.AT.PRESENT,1,1),ylog,60) else NA
df$TAX_TE    <- if ("TAX.CLASS.AT.PRESENT"%in%names(df)) target_enc(df$TAX.CLASS.AT.PRESENT,ylog,50) else NA

df$BCLASS_R <- if("BUILDING.CLASS.AT.PRESENT"%in%names(df))
                 as.numeric(substr(df$BUILDING.CLASS.AT.PRESENT,1,1)=="R") else 0
df$TAX_2C   <- if("TAX.CLASS.AT.PRESENT"%in%names(df))
                 as.numeric(df$TAX.CLASS.AT.PRESENT%in%c("2C","02C")) else 0
df$TAX_2    <- if("TAX.CLASS.AT.PRESENT"%in%names(df))
                 as.numeric(df$TAX.CLASS.AT.PRESENT%in%c("2","02")) else 0

# ---- NEW 1: relative size within ZIP (1 df) ----
if (!all(is.na(df$ZIP))) {
  zip_med <- tapply(df$LOG_GSF, df$ZIP, function(v) median(v, na.rm=TRUE))
  df$REL_SIZE_ZIP <- df$LOG_GSF - as.numeric(zip_med[df$ZIP])
  df$REL_SIZE_ZIP[!is.finite(df$REL_SIZE_ZIP)] <- 0
} else df$REL_SIZE_ZIP <- 0

# ---- NEW 2: timing × neighborhood TE (1 df) ----
df$TIxNE <- with(df, TIME_INDEX * NEIGH_TE)

# ---- NEW 3: very-old stock hinge (1 df; hinge ~50y on original scale) ----
cut_age <- log1p(50)
df$AGE_HINGE <- pmax(0, df$AGE_LOG - cut_age)

# model data (keep ≥13k)
feats <- c("LOG_GSF","LOG_LSF","AGE_LOG","AGE_HINGE","FAR","TIME_INDEX",
           "NEIGH_TE","BCLASS_TE","TAX_TE","BCLASS_R","TAX_2","TAX_2C",
           "REL_SIZE_ZIP","TIxNE")
dfm <- df[complete.cases(df[,c("SALE.PRICE",feats)]), c("SALE.PRICE",feats)]
stopifnot(nrow(dfm) >= 13000)

# ---- formula: keep your spine + previous interactions + 3 new terms ----
terms <- c(
  "ns(LOG_GSF,4)","ns(LOG_LSF,2)","ns(AGE_LOG,2)","AGE_HINGE","ns(TIME_INDEX,3)","FAR",
  "NEIGH_TE","BCLASS_TE","TAX_TE","BCLASS_R","TAX_2","TAX_2C",
  "REL_SIZE_ZIP","TIxNE",
  "LOG_GSF:TAX_2","LOG_GSF:BCLASS_R","FAR:LOG_GSF"   # from your strong variant
)
form_full <- as.formula(paste("log(SALE.PRICE) ~", paste(terms, collapse=" + ")))

# ---- keep ≤40 df (shrink splines first, then drop weakest new terms if needed) ----
reduce_to40 <- function(f,d){
  cur <- f
  drop_order <- c("TIxNE","REL_SIZE_ZIP","AGE_HINGE","FAR:LOG_GSF","LOG_GSF:TAX_2","LOG_GSF:BCLASS_R")
  for (iter in 1:120){
    k <- try(count_df(cur,d), silent=TRUE)
    if (!inherits(k,"try-error") && k <= 40) return(cur)
    # step 1: reduce LOG_GSF spline 4->3->2
    fstr <- deparse(cur)
    if (grepl("ns\\(LOG_GSF,4\\)", fstr)) { fstr <- gsub("ns\\(LOG_GSF,4\\)","ns(LOG_GSF,3)", fstr)
    } else if (grepl("ns\\(LOG_GSF,3\\)", fstr)) { fstr <- gsub("ns\\(LOG_GSF,3\\)","ns(LOG_GSF,2)", fstr)
    } else {
      # step 2: drop lowest-priority single-df terms one by one
      for (t in drop_order) {
        if (grepl(paste0("(^|\\+|\\s)", gsub("\\+","\\\\+",t), "($|\\+|\\s)"), fstr)) {
          fstr <- sub(paste0("\\+\\s*", t), "", fstr); break
        }
      }
    }
    cur <- as.formula(fstr)
  }
  cur
}
form <- reduce_to40(form_full, dfm)

# ---- fit two variants: robust vs. OLS, pick lower RMSE (tie -> higher R^2) ----
fit_and_eval <- function(dat, form, robust = FALSE, k = 1.5){
  # base fit (unweighted) to get residual scale
  m0 <- lm(form, data = dat)
  if (robust){
    # standardized residuals
    sig <- summary(m0)$sigma
    u   <- residuals(m0) / sig

    # Huber weights: w = min(1, k/|u|)
    w    <- pmin(1, k / pmax(1e-8, abs(u)))
    w[!is.finite(w)] <- 1

    # attach to data so lm can see it
    dat$.__w <- w
    m <- lm(form, data = dat, weights = .__w, na.action = na.exclude)
  } else {
    m <- m0
  }

  # predictions on log-scale, then Duan smearing to get dollars
  pred_log <- predict(m, newdata = dat, na.action = na.exclude)
  smear    <- mean(exp(residuals(m)), na.rm = TRUE)
  yhat     <- exp(pred_log) * smear
  y        <- dat$SALE.PRICE

  rmse  <- sqrt(mean((y - yhat)^2, na.rm = TRUE))
  mae   <- mean(abs(y - yhat), na.rm = TRUE)
  r2    <- 1 - sum((y - yhat)^2, na.rm = TRUE) / sum((y - mean(y, na.rm = TRUE))^2, na.rm = TRUE)
  dfmod <- ncol(model.matrix(form, dat)) - 1L

  list(m = m, smear = smear, rmse = rmse, mae = mae, r2 = r2,
       adjr2 = summary(m)$adj.r.squared, df = dfmod)
}
res_ols  <- fit_and_eval(dfm, form, robust=FALSE)
res_rob  <- fit_and_eval(dfm, form, robust=TRUE)
best <- if (res_rob$rmse + 1e-9 < res_ols$rmse) res_rob else if (abs(res_rob$rmse-res_ols$rmse)<1e-9 && res_rob$r2>res_ols$r2) res_rob else res_ols

cat("\n=========== LAST-MILE++ RESULTS ===========\n")

=========== LAST-MILE++ RESULTS ===========
cat("Formula:\n  ", deparse(form), "\n\n")
Formula:
   log(SALE.PRICE) ~ ns(LOG_GSF, 4) + ns(LOG_LSF, 2) + ns(AGE_LOG,      2) + AGE_HINGE + ns(TIME_INDEX, 3) + FAR + NEIGH_TE + BCLASS_TE +      TAX_TE + BCLASS_R + TAX_2 + TAX_2C + REL_SIZE_ZIP + TIxNE +      LOG_GSF:TAX_2 + LOG_GSF:BCLASS_R + FAR:LOG_GSF 
cat(sprintf("Rows: %d (≥13k) | df: %d (≤40)\n", nrow(dfm), best$df))
Rows: 13985 (≥13k) | df: 24 (≤40)
cat(sprintf("Adj R^2 (log): %.3f | Duan smearing: %.4f | Fit: %s\n",
            best$adjr2, best$smear, if (identical(best,res_rob)) "robust" else "OLS"))
Adj R^2 (log): 0.215 | Duan smearing: 1.1423 | Fit: robust
cat(sprintf("$-scale RMSE: $%s | MAE: $%s | R^2: %.3f\n",
            format(round(best$rmse), big.mark=","), format(round(best$mae), big.mark=","), best$r2))
$-scale RMSE: $464,722 | MAE: $275,386 | R^2: 0.601
# ==== COEFFICIENT TABLE & INFERENCE (for the chosen 'best$m') ====

pretty_stars <- function(p){
  cut(p, breaks=c(-Inf, .001, .01, .05, .1, Inf),
      labels=c("***","**","*","."," "), right=FALSE)
}

summ   <- summary(best$m)
coeft  <- as.data.frame(summ$coefficients)
names(coeft) <- c("Estimate","Std.Error","t.value","Pr(>|t|)")
coeft$Signif <- pretty_stars(coeft[["Pr(>|t|)"]])

# 95% CIs (base-R; works for weighted lm as well)
ci <- try(confint(best$m), silent=TRUE)
if (!inherits(ci, "try-error")) {
  coeft$CI_2.5  <- ci[rownames(coeft), 1]
  coeft$CI_97.5 <- ci[rownames(coeft), 2]
}

# Order by absolute t
coeft$Abs_t <- abs(coeft$`t.value`)
coeft <- coeft[order(-coeft$Abs_t), ]

cat("\n=========== COEFFICIENT TABLE (sorted by |t|) ===========\n")

=========== COEFFICIENT TABLE (sorted by |t|) ===========
printCoefmat(as.matrix(coeft[, c("Estimate","Std.Error","t.value","Pr(>|t|)")]),
             P.values=TRUE, has.Pvalue=TRUE, signif.stars=FALSE, digits=4)
                   Estimate Std.Error t.value Pr(>|t|)
NEIGH_TE            1.10981   0.04735  23.440  < 2e-16
ns(AGE_LOG, 2)1    -1.66316   0.26285  -6.328 2.57e-10
REL_SIZE_ZIP        0.49319   0.07989   6.174 6.86e-10
ns(AGE_LOG, 2)2    -3.01867   0.49800  -6.062 1.38e-09
AGE_HINGE           0.75361   0.12746   5.913 3.45e-09
ns(TIME_INDEX, 3)2  5.19921   1.26457   4.111 3.95e-05
ns(TIME_INDEX, 3)1  2.41292   0.59423   4.061 4.92e-05
ns(TIME_INDEX, 3)3  3.69154   0.92786   3.979 6.97e-05
TIxNE              -0.06384   0.01687  -3.784 0.000155
ns(LOG_LSF, 2)2     0.51394   0.20014   2.568 0.010242
BCLASS_TE          -3.24065   1.64281  -1.973 0.048558
ns(LOG_GSF, 4)3     1.20838   0.64211   1.882 0.059872
TAX_TE              0.50637   0.27325   1.853 0.063881
TAX_2:LOG_GSF       0.14987   0.09172   1.634 0.102265
TAX_2              -1.05791   0.65800  -1.608 0.107910
ns(LOG_GSF, 4)1     0.45456   0.29230   1.555 0.119943
BCLASS_R:LOG_GSF    0.14079   0.09200   1.530 0.125962
(Intercept)        34.15769  22.34039   1.529 0.126296
ns(LOG_GSF, 4)4     0.53788   0.38967   1.380 0.167499
ns(LOG_GSF, 4)2     0.31910   0.23838   1.339 0.180719
FAR:LOG_GSF        -0.02041   0.01632  -1.250 0.211282
FAR                 0.14036   0.13534   1.037 0.299697
TAX_2C             -0.03878   0.11326  -0.342 0.732043
ns(LOG_LSF, 2)1    -0.09459   0.41667  -0.227 0.820415
cat("\nSignif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1\n")

Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
# Add stars & CIs in a tidy view
cat("\nTop 25 by |t| (with stars & 95% CI):\n")

Top 25 by |t| (with stars & 95% CI):
print(utils::head(coeft[, c("Estimate","Std.Error","t.value","Pr(>|t|)","Signif","CI_2.5","CI_97.5")], 25), digits=4)

# Model overview again for convenience
cat("\n=========== MODEL OVERVIEW ===========\n")

=========== MODEL OVERVIEW ===========
cat(sprintf("Observations: %d\n", nobs(best$m)))
Observations: 13985
cat(sprintf("Model df (excl. intercept): %d\n", best$df))
Model df (excl. intercept): 24
cat(sprintf("Adj R^2 (log): %.3f | Duan smearing: %.4f | Fit: %s\n",
            best$adjr2, best$smear, if (identical(best$rmse, res_rob$rmse) && identical(best$r2, res_rob$r2)) "robust" else "OLS"))
Adj R^2 (log): 0.215 | Duan smearing: 1.1423 | Fit: robust
cat(sprintf("$-scale RMSE: $%s | MAE: $%s | R^2: %.3f\n",
            format(round(best$rmse), big.mark=","), format(round(best$mae), big.mark=","), best$r2))
$-scale RMSE: $464,722 | MAE: $275,386 | R^2: 0.601
# Optional: write to CSV for the writeup
out_tab <- coeft[, c("Estimate","Std.Error","t.value","Pr(>|t|)","Signif","CI_2.5","CI_97.5","Abs_t")]
utils::write.csv(out_tab, "last_mile_coefficients.csv", row.names=TRUE)
cat("\nSaved coefficients to: last_mile_coefficients.csv\n")

Saved coefficients to: last_mile_coefficients.csv
LS0tCnRpdGxlOiAiYXNzaWdubWVudF8zX3NlY3Rpb24xJjIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCgpgYGB7cn0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgSS4gREFUQSAmIExJQlJBUlkgTE9BRElORwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCnNldHdkKCJ+L0Rlc2t0b3AvU3RhdGlzdGljYWwgTW9kZWwvYXNzaWdubWVudF8zIikKCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKCiMgTG9hZCBEYXRhCnJhd19yZWFsX2VzdGF0ZV8yMDE2IDwtIHJlYWQuY3N2KCIyMDE2X2Jyb29rbHluLmNzdiIpCnJhd19yZWFsX2VzdGF0ZV8yMDE3IDwtIHJlYWQuY3N2KCIyMDE3X2Jyb29rbHluLmNzdiIpCnJhd19yZWFsX2VzdGF0ZV8yMDE4IDwtIHJlYWQuY3N2KCIyMDE4X2Jyb29rbHluLmNzdiIpCnJhd19yZWFsX2VzdGF0ZV8yMDE5IDwtIHJlYWQuY3N2KCIyMDE5X2Jyb29rbHluLmNzdiIpCnJhd19yZWFsX2VzdGF0ZV8yMDIwIDwtIHJlYWQuY3N2KCIyMDIwX2Jyb29rbHluLmNzdiIpCgojIENoZWNrIENvbHVtbiBOYW1lcwpjb2xuYW1lcyhyYXdfcmVhbF9lc3RhdGVfMjAxNikKY29sbmFtZXMocmF3X3JlYWxfZXN0YXRlXzIwMTcpCmNvbG5hbWVzKHJhd19yZWFsX2VzdGF0ZV8yMDE4KQpjb2xuYW1lcyhyYXdfcmVhbF9lc3RhdGVfMjAxOSkKY29sbmFtZXMocmF3X3JlYWxfZXN0YXRlXzIwMjApCgojIENoZWNrIFJhdyBEYXRhCmhlYWQocmF3X3JlYWxfZXN0YXRlXzIwMTYpCmhlYWQocmF3X3JlYWxfZXN0YXRlXzIwMTcpCmhlYWQocmF3X3JlYWxfZXN0YXRlXzIwMTgpCmhlYWQocmF3X3JlYWxfZXN0YXRlXzIwMTkpCmhlYWQocmF3X3JlYWxfZXN0YXRlXzIwMjApCgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBJSS4gQ0xFQU4gJiBDT01CSU5FIERBVEEKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFJlbW92ZSBNZXRhZGF0YSBSb3dzCnJlYWxfZXN0YXRlXzIwMTYgPC0gcmF3X3JlYWxfZXN0YXRlXzIwMTZbLSgxOjQpLCBdCnJlYWxfZXN0YXRlXzIwMTcgPC0gcmF3X3JlYWxfZXN0YXRlXzIwMTdbLSgxOjQpLCBdCnJlYWxfZXN0YXRlXzIwMTggPC0gcmF3X3JlYWxfZXN0YXRlXzIwMThbLSgxOjQpLCBdCnJlYWxfZXN0YXRlXzIwMTkgPC0gcmF3X3JlYWxfZXN0YXRlXzIwMTlbLSgxOjQpLCBdCnJlYWxfZXN0YXRlXzIwMjAgPC0gcmF3X3JlYWxfZXN0YXRlXzIwMjBbLSgxOjcpLCBdCgpoZWFkKHJlYWxfZXN0YXRlXzIwMTYpCmhlYWQocmVhbF9lc3RhdGVfMjAxNykKaGVhZChyZWFsX2VzdGF0ZV8yMDE4KQpoZWFkKHJlYWxfZXN0YXRlXzIwMTkpCmhlYWQocmVhbF9lc3RhdGVfMjAyMCkKCiMgU3RhbmRhcmRpemUgQ29sdW1uIE5hbWVzCnN0YW5kYXJkaXplZF9jb2x1bW5fbmFtZXMgPC0gYygiQk9ST1VHSCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAiTkVJR0hCT1JIT09EIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIkJVSUxESU5HIENMQVNTIENBVEVHT1JZIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJUQVggQ0xBU1MgQVQgUFJFU0VOVCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAiQkxPQ0siLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxPVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJFQVNFLU1FTlQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiQlVJTERJTkcgQ0xBU1MgQVQgUFJFU0VOVCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJBRERSRVNTIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIkFQQVJUTUVOVCBOVU1CRVIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiWklQIENPREUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiUkVTSURFTlRJQUwgVU5JVFMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiQ09NTUVSQ0lBTCBVTklUUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJUT1RBTCBVTklUUyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJMQU5EIFNRVUFSRSBGRUVUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIkdST1NTIFNRVUFSRSBGRUVUIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIllFQVIgQlVJTFQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiVEFYIENMQVNTIEFUIFRJTUUgT0YgU0FMRSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJCVUlMRElORyBDTEFTUyBBVCBUSU1FIE9GIFNBTEUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiU0FMRSBQUklDRSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJTQUxFIERBVEUiKQoKY29sbmFtZXMocmVhbF9lc3RhdGVfMjAxNikgPC0gc3RhbmRhcmRpemVkX2NvbHVtbl9uYW1lcwpjb2xuYW1lcyhyZWFsX2VzdGF0ZV8yMDE3KSA8LSBzdGFuZGFyZGl6ZWRfY29sdW1uX25hbWVzCmNvbG5hbWVzKHJlYWxfZXN0YXRlXzIwMTgpIDwtIHN0YW5kYXJkaXplZF9jb2x1bW5fbmFtZXMKY29sbmFtZXMocmVhbF9lc3RhdGVfMjAxOSkgPC0gc3RhbmRhcmRpemVkX2NvbHVtbl9uYW1lcwpjb2xuYW1lcyhyZWFsX2VzdGF0ZV8yMDIwKSA8LSBzdGFuZGFyZGl6ZWRfY29sdW1uX25hbWVzCgpoZWFkKHJlYWxfZXN0YXRlXzIwMTYpCmhlYWQocmVhbF9lc3RhdGVfMjAxNykKaGVhZChyZWFsX2VzdGF0ZV8yMDE4KQpoZWFkKHJlYWxfZXN0YXRlXzIwMTkpCmhlYWQocmVhbF9lc3RhdGVfMjAyMCkKCiMgU2V0IENvbHVtbiBEYXRhIFR5cGVzCnNldF9jb2x1bW5fdHlwZXMgPC0gZnVuY3Rpb24oZGYpIHsKICBzdHJpbmdfY29scyA8LSBjKAogICAgIkJPUk9VR0giLCAiTkVJR0hCT1JIT09EIiwgIkJVSUxESU5HIENMQVNTIENBVEVHT1JZIiwgCiAgICAiVEFYIENMQVNTIEFUIFBSRVNFTlQiLCAiQkxPQ0siLCAiRUFTRS1NRU5UIiwgCiAgICAiQlVJTERJTkcgQ0xBU1MgQVQgUFJFU0VOVCIsICJBRERSRVNTIiwgIkFQQVJUTUVOVCBOVU1CRVIiLCAKICAgICJaSVAgQ09ERSIsICJUQVggQ0xBU1MgQVQgVElNRSBPRiBTQUxFIiwgCiAgICAiQlVJTERJTkcgQ0xBU1MgQVQgVElNRSBPRiBTQUxFIgogICkKICAKICBudW1lcmljX2NvbHMgPC0gYygKICAgICJSRVNJREVOVElBTCBVTklUUyIsICJDT01NRVJDSUFMIFVOSVRTIiwgIlRPVEFMIFVOSVRTIiwKICAgICJMQU5EIFNRVUFSRSBGRUVUIiwgIkdST1NTIFNRVUFSRSBGRUVUIiwgIllFQVIgQlVJTFQiLCAiU0FMRSBQUklDRSIKICApCiAgCiAgIyBDbGVhbiBhbmQgY29udmVydCBzdHJpbmcvbnVtZXJpYyBjb2x1bW5zIGZpcnN0CiAgZGYgPC0gZGYgJT4lCiAgICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIHN0cl9zcXVpc2gpKSAlPiUKICAgIG11dGF0ZShhY3Jvc3MoYWxsX29mKHN0cmluZ19jb2xzKSwgYXMuY2hhcmFjdGVyKSkgJT4lCiAgICBtdXRhdGUoYWNyb3NzKGFsbF9vZihudW1lcmljX2NvbHMpLAogICAgICAgICAgICAgICAgICB+IHN1cHByZXNzV2FybmluZ3MoYXMubnVtZXJpYyhnc3ViKCJbXjAtOS5dIiwgIiIsIHN0cl9zcXVpc2goLikpKSkpKQogIAogICMgSGFuZGxlIFNBTEUgREFURSBmb3JtYXQgZmxleGlibHkgKDItZGlnaXQgdnMgNC1kaWdpdCB5ZWFyKQogIGlmIChhbnkoZ3JlcGwoIl5cXGR7MSwyfS9cXGR7MSwyfS9cXGR7Mn0kIiwgZGYkYFNBTEUgREFURWApKSkgewogICAgZGYkYFNBTEUgREFURWAgPC0gYXMuRGF0ZShkZiRgU0FMRSBEQVRFYCwgZm9ybWF0ID0gIiVtLyVkLyV5IikgICMg4pyFIDItZGlnaXQgeWVhcgogIH0gZWxzZSB7CiAgICBkZiRgU0FMRSBEQVRFYCA8LSBhcy5EYXRlKGRmJGBTQUxFIERBVEVgLCBmb3JtYXQgPSAiJW0vJWQvJVkiKSAgIyDinIUgNC1kaWdpdCB5ZWFyCiAgfQogIAogIHJldHVybihkZikKfQoKCgoKcmVhbF9lc3RhdGVfMjAxNiA8LSBzZXRfY29sdW1uX3R5cGVzKHJlYWxfZXN0YXRlXzIwMTYpCnJlYWxfZXN0YXRlXzIwMTcgPC0gc2V0X2NvbHVtbl90eXBlcyhyZWFsX2VzdGF0ZV8yMDE3KQpyZWFsX2VzdGF0ZV8yMDE4IDwtIHNldF9jb2x1bW5fdHlwZXMocmVhbF9lc3RhdGVfMjAxOCkKcmVhbF9lc3RhdGVfMjAxOSA8LSBzZXRfY29sdW1uX3R5cGVzKHJlYWxfZXN0YXRlXzIwMTkpCnJlYWxfZXN0YXRlXzIwMjAgPC0gc2V0X2NvbHVtbl90eXBlcyhyZWFsX2VzdGF0ZV8yMDIwKQoKaGVhZChyZWFsX2VzdGF0ZV8yMDE2KQpoZWFkKHJlYWxfZXN0YXRlXzIwMTcpCmhlYWQocmVhbF9lc3RhdGVfMjAxOCkKaGVhZChyZWFsX2VzdGF0ZV8yMDE5KQpoZWFkKHJlYWxfZXN0YXRlXzIwMjApCgojIEZpbHRlciBkYXRhIAojIGZpcnN0IGxldHRlciBvZiBCVUlMRElORyBDTEFTUyBBVCBUSU1FIE9GIFNBTEUgaW4gYyAoIkEiLCAiUikKIyBBTkQgbnVtYmVyIG9mIFRPVEFMIFVOSVRTID09IDEKIyBBTkQgbnVtYmVyIG9mIFJFU0lERU5USUFMIFVOSVRTID09IDEKIyBBTkQgR1JPU1MgU1FVQVJFIEZPT1QgPiAwCiMgQU5EIFNBTEUgUFJJQ0UgaXMgbm90IE4vQQoKZmlsdGVyX2RhdGEgPC0gZnVuY3Rpb24oZGYpIHsKICBkZiAlPiUKICAgIGZpbHRlcigKICAgICAgc3Vic3RyKGBCVUlMRElORyBDTEFTUyBBVCBUSU1FIE9GIFNBTEVgLCAxLCAxKSAlaW4lIGMoIkEiLCAiUiIpLAogICAgICBgVE9UQUwgVU5JVFNgID09IDEsCiAgICAgIGBSRVNJREVOVElBTCBVTklUU2AgPT0gMSwKICAgICAgYEdST1NTIFNRVUFSRSBGRUVUYCA+IDAsCiAgICAgICFpcy5uYShgU0FMRSBQUklDRWApCiAgICApCn0KCnJlYWxfZXN0YXRlXzIwMTYgPC0gZmlsdGVyX2RhdGEocmVhbF9lc3RhdGVfMjAxNikKcmVhbF9lc3RhdGVfMjAxNyA8LSBmaWx0ZXJfZGF0YShyZWFsX2VzdGF0ZV8yMDE3KQpyZWFsX2VzdGF0ZV8yMDE4IDwtIGZpbHRlcl9kYXRhKHJlYWxfZXN0YXRlXzIwMTgpCnJlYWxfZXN0YXRlXzIwMTkgPC0gZmlsdGVyX2RhdGEocmVhbF9lc3RhdGVfMjAxOSkKcmVhbF9lc3RhdGVfMjAyMCA8LSBmaWx0ZXJfZGF0YShyZWFsX2VzdGF0ZV8yMDIwKQoKaGVhZChyZWFsX2VzdGF0ZV8yMDE2KQpoZWFkKHJlYWxfZXN0YXRlXzIwMTcpCmhlYWQocmVhbF9lc3RhdGVfMjAxOCkKaGVhZChyZWFsX2VzdGF0ZV8yMDE5KQpoZWFkKHJlYWxfZXN0YXRlXzIwMjApCgojIENyZWF0ZSBOZXcgIkFnZSIgY29sdW1uCmRhdGFzZXRzIDwtIGxpc3QoCiAgcmVhbF9lc3RhdGVfMjAxNiA9IHJlYWxfZXN0YXRlXzIwMTYsCiAgcmVhbF9lc3RhdGVfMjAxNyA9IHJlYWxfZXN0YXRlXzIwMTcsCiAgcmVhbF9lc3RhdGVfMjAxOCA9IHJlYWxfZXN0YXRlXzIwMTgsCiAgcmVhbF9lc3RhdGVfMjAxOSA9IHJlYWxfZXN0YXRlXzIwMTksCiAgcmVhbF9lc3RhdGVfMjAyMCA9IHJlYWxfZXN0YXRlXzIwMjAKKQoKZm9yICh5ZWFyIGluIDIwMTY6MjAyMCkgewogIGRmX25hbWUgPC0gcGFzdGUwKCJyZWFsX2VzdGF0ZV8iLCB5ZWFyKQogIGRmIDwtIGdldChkZl9uYW1lKQogIAogIGRmJGBTQUxFIERBVEVgIDwtIGFzLkRhdGUoZGYkYFNBTEUgREFURWApCiAgZGYkYFNBTEUgWUVBUmAgPC0gYXMubnVtZXJpYyhmb3JtYXQoZGYkYFNBTEUgREFURWAsICIlWSIpKQogIAogIGRmJGBCVUlMRElORyBBR0UgQVQgU0FMRWAgPC0gaWZlbHNlKAogICAgaXMubmEoZGYkYFlFQVIgQlVJTFRgKSB8IGRmJGBZRUFSIEJVSUxUYCA9PSAwLAogICAgTkEsCiAgICBkZiRgU0FMRSBZRUFSYCAtIGRmJGBZRUFSIEJVSUxUYAogICkKICBhc3NpZ24oZGZfbmFtZSwgZGYpCn0KCgojIENvbWJpbmUgQWxsIERhdGEKcmVhbF9lc3RhdGVfZGYgPC0gcmJpbmQocmVhbF9lc3RhdGVfMjAxNiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHJlYWxfZXN0YXRlXzIwMTcsIAogICAgICAgICAgICAgICAgICAgICAgICByZWFsX2VzdGF0ZV8yMDE4LCAKICAgICAgICAgICAgICAgICAgICAgICAgcmVhbF9lc3RhdGVfMjAxOSwKICAgICAgICAgICAgICAgICAgICAgICAgcmVhbF9lc3RhdGVfMjAyMCkKCmhlYWQocmVhbF9lc3RhdGVfZGYpCgojIERyb3AgSXJyZWxldmFudCBjb2x1bW5zCnJlYWxfZXN0YXRlX2RmIDwtIHJlYWxfZXN0YXRlX2RmWyAsICEobmFtZXMocmVhbF9lc3RhdGVfMjAxNikgJWluJSBjKAogICJCT1JPVUdIIiwgCiAgIkVBU0UtTUVOVCIsIAogICJBUEFSVE1FTlQgTlVNQkVSIiwKICAiVE9UQUwgVU5JVFMiLCAjIEFsd2F5cyAxCiAgIlJFU0lERU5USUFMIFVOSVRTIiwgIyBBbHdheXMgMQogICJDT01NRVJDSUFMIFVOSVRTIiwgCiAgIkFERFJFU1MgTlVNQkVSIiwKICAiU0FMRSBZRUFSIgopKV0KCiMgQ2hlY2sgUm93IApoZWFkKHJlYWxfZXN0YXRlX2RmKQpucm93KHJlYWxfZXN0YXRlX2RmKQoKYGBgCgpgYGB7cn0KIyMgQnVpbGQgY2xlYW5lZCBkYXRhIHJlYWR5IGZvciBvbmUtbGluZSByZWdyZXNzaW9ucwojIyBJbnB1dDogcmVhbF9lc3RhdGVfZGYKCiMgMSkgQ29weSBhbmQgbWFrZSBuYW1lcyBSLXNhZmUgKGhhbmRsZXMgc3BhY2VzKQpkZiA8LSByZWFsX2VzdGF0ZV9kZgpuYW1lcyhkZikgPC0gbWFrZS5uYW1lcyhuYW1lcyhkZikpCgojIDIpIElkZW50aWZ5IHRoZSBwcmljZSBjb2x1bW4gcm9idXN0bHkgYW5kIGNvZXJjZSB0byBudW1lcmljIGlmIG5lZWRlZApwcmljZV9jb2wgPC0gaWYgKCJTQUxFLlBSSUNFIiAlaW4lIG5hbWVzKGRmKSkgIlNBTEUuUFJJQ0UiIGVsc2UgewogIGNhbmQgPC0gZ3JlcCgiXlNBTEVbLl8gXT9QUklDRSQiLCBuYW1lcyhkZiksIGlnbm9yZS5jYXNlID0gVFJVRSwgdmFsdWUgPSBUUlVFKQogIHN0b3BpZm5vdChsZW5ndGgoY2FuZCkgPiAwKTsgY2FuZFsxXQp9CmlmIChpcy5jaGFyYWN0ZXIoZGZbW3ByaWNlX2NvbF1dKSkgewogIGRmW1twcmljZV9jb2xdXSA8LSBhcy5udW1lcmljKGdzdWIoIlteMC05Ll0iLCAiIiwgZGZbW3ByaWNlX2NvbF1dKSkKfQoKIyAzKSBEcm9wIG9idmlvdXMgSURzL2RhdGVzIChvcHRpb25hbCBidXQgdHlwaWNhbCkKZHJvcF9jb2xzIDwtIGludGVyc2VjdChjKCJBRERSRVNTIiwiQkxPQ0siLCJMT1QiLCJTQUxFLkRBVEUiKSwgbmFtZXMoZGYpKQppZiAobGVuZ3RoKGRyb3BfY29scykpIGRmW2Ryb3BfY29sc10gPC0gbGlzdChOVUxMKQoKIyA0KSBDb252ZXJ0IEFMTCBjaGFyYWN0ZXIgY29scyB0byBmYWN0b3JzIChzbyBsbSgpIG1ha2VzIGR1bW1pZXMpCmNoYXJfY29scyA8LSBuYW1lcyhkZilbc2FwcGx5KGRmLCBpcy5jaGFyYWN0ZXIpXQppZiAobGVuZ3RoKGNoYXJfY29scykpIGRmW2NoYXJfY29sc10gPC0gbGFwcGx5KGRmW2NoYXJfY29sc10sIGFzLmZhY3RvcikKCiMgNSkgRHJvcCBzaW5nbGUtbGV2ZWwgY29sdW1ucyAoYXZvaWRzIGNvbnRyYXN0cyBlcnJvcnMpCm9uZV9sZXZlbCA8LSBuYW1lcyhkZilbc2FwcGx5KGRmLCBmdW5jdGlvbih4KSBsZW5ndGgodW5pcXVlKG5hLm9taXQoeCkpKSA8PSAxKV0KaWYgKGxlbmd0aChvbmVfbGV2ZWwpKSBkZltvbmVfbGV2ZWxdIDwtIGxpc3QoTlVMTCkKCiMgNikgS2VlcCByb3dzIHdpdGggZmluaXRlIHByaWNlczsgYWxzbyBtYWtlIGEgbG9nLXJlYWR5IHZlcnNpb24gKD4wKQpkZl9yZWFkeSAgICAgPC0gc3Vic2V0KGRmLCBpcy5maW5pdGUoZGZbW3ByaWNlX2NvbF1dKSkKZGZfcmVhZHlfbG9nIDwtIHN1YnNldChkZl9yZWFkeSwgZGZfcmVhZHlbW3ByaWNlX2NvbF1dID4gMCkKCmNhdCgiZGZfcmVhZHk6IiwgbnJvdyhkZl9yZWFkeSksICJyb3dzLCIsIG5jb2woZGZfcmVhZHkpLCAiY29sc1xuIikKY2F0KCJkZl9yZWFkeV9sb2c6IiwgbnJvdyhkZl9yZWFkeV9sb2cpLCAicm93c1xuIikKYGBgCgpgYGB7cn0KIyA9PT09PT09PT09PT0gTEFTVC1NSUxFKysgKOKJpDQwIGRmLCDiiaUxM2sgcm93cyk6IFpJUC1yZWxhdGl2ZSBzaXplLCB0aW1pbmfDl25laWdoLCBhZ2UgaGluZ2UgPT09PT09PT09PT09CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7IGxpYnJhcnkoZHBseXIpOyBsaWJyYXJ5KHNwbGluZXMpOyBsaWJyYXJ5KE1BU1MpIH0pCgp3aW5zICA8LSBmdW5jdGlvbih4LCBxbG89LjAxLCBxaGk9Ljk5KXsgcSA8LSBxdWFudGlsZSh4LGMocWxvLHFoaSksbmEucm09VFJVRSk7IHBtaW4ocVsyXSxwbWF4KHFbMV0seCkpIH0Kcm1zZSAgPC0gZnVuY3Rpb24oYSxiKSBzcXJ0KG1lYW4oKGEtYileMixuYS5ybT1UUlVFKSkKbWFlICAgPC0gZnVuY3Rpb24oYSxiKSBtZWFuKGFicyhhLWIpLG5hLnJtPVRSVUUpCmNvdW50X2RmIDwtIGZ1bmN0aW9uKGYsZCkgbmNvbChtb2RlbC5tYXRyaXgoZixkKSktMUwKCiMgLS0tLSBkYXRhICYgY29yZSBudW1lcmljcyAtLS0tCmRmIDwtIHJlYWxfZXN0YXRlX2RmCm5hbWVzKGRmKSA8LSBtYWtlLm5hbWVzKG5hbWVzKGRmKSkKaWYgKGlzLmNoYXJhY3RlcihkZiRTQUxFLlBSSUNFKSkgZGYkU0FMRS5QUklDRSA8LSBhcy5udW1lcmljKGdzdWIoIlteMC05Ll0iLCIiLGRmJFNBTEUuUFJJQ0UpKQpkZiA8LSBkZiAlPiUgZmlsdGVyKGlzLmZpbml0ZShTQUxFLlBSSUNFKSwgU0FMRS5QUklDRT4wKQoKZGYgPC0gZGYgJT4lCiAgbXV0YXRlKAogICAgR1NGID0gcG1heCgxLCBzdXBwcmVzc1dhcm5pbmdzKGFzLm51bWVyaWMoR1JPU1MuU1FVQVJFLkZFRVQpKSksCiAgICBMU0YgPSBwbWF4KDEsIHN1cHByZXNzV2FybmluZ3MoYXMubnVtZXJpYyhMQU5ELlNRVUFSRS5GRUVUKSkpLAogICAgTE9HX0dTRiA9IGxvZyhHU0YpLCBMT0dfTFNGID0gbG9nKExTRiksCiAgICBBR0UgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICAhaXMubmEoQlVJTERJTkcuQUdFLkFULlNBTEUpIH4gc3VwcHJlc3NXYXJuaW5ncyhhcy5udW1lcmljKEJVSUxESU5HLkFHRS5BVC5TQUxFKSksCiAgICAgICFpcy5uYShZRUFSLkJVSUxUKSB+IGFzLm51bWVyaWMoZm9ybWF0KFN5cy5EYXRlKCksIiVZIikpIC0gc3VwcHJlc3NXYXJuaW5ncyhhcy5udW1lcmljKFlFQVIuQlVJTFQpKSwKICAgICAgVFJVRSB+IE5BX3JlYWxfCiAgICApLAogICAgQUdFX0xPRyA9IGxvZzFwKHBtYXgoMCwgQUdFKSksCiAgICBGQVIgPSBwbWluKDUsIHBtYXgoMC4wNSwgR1NGIC8gcG1heCgxLCBMU0YpKSksCiAgICBTQUxFLkRBVEUgPSBzdXBwcmVzc1dhcm5pbmdzKGFzLkRhdGUoaWYgKCJTQUxFLkRBVEUiICVpbiUgbmFtZXMoZGYpKSBTQUxFLkRBVEUgZWxzZSBOQSkpLAogICAgVElNRV9JTkRFWCA9IGFzLm51bWVyaWMoZGlmZnRpbWUoU0FMRS5EQVRFLCBtaW4oU0FMRS5EQVRFLCBuYS5ybT1UUlVFKSwgdW5pdHM9ImRheXMiKSkvMzY1LAogICAgWklQID0gaWYgKCJaSVAuQ09ERSIgJWluJSBuYW1lcyhkZikpIGFzLmNoYXJhY3RlcihaSVAuQ09ERSkgZWxzZSBOQV9jaGFyYWN0ZXJfCiAgKSAlPiUKICBtdXRhdGUoYWNyb3NzKGMoU0FMRS5QUklDRSxHU0YsTFNGLEZBUiksIHdpbnMpKQoKIyAtLS0tIHNpbXBsZSB0YXJnZXQgZW5jb2RpbmdzIChpbi1iYWcgc2hyaW5rZWQ7IHdlIGFscmVhZHkgdmFsaWRhdGVkIG9vZiBlYXJsaWVyKSAtLS0tCnlsb2cgPC0gbG9nKGRmJFNBTEUuUFJJQ0UpCnRhcmdldF9lbmMgPC0gZnVuY3Rpb24oayx5LG09NzApewogIG11PC1tZWFuKHksbmEucm09VFJVRSk7IGtleTwtYXMuY2hhcmFjdGVyKGspCiAgdDwtdGFwcGx5KHksa2V5LGZ1bmN0aW9uKHYpe248LXN1bShpcy5maW5pdGUodikpOyhzdW0odixuYS5ybT1UUlVFKSttKm11KS8ocG1heCgxLG4pK20pfSkKICBvdXQ8LWFzLm51bWVyaWModFtrZXldKTsgb3V0WyFpcy5maW5pdGUob3V0KV08LW11OyBvdXQKfQpkZiRORUlHSF9URSAgPC0gaWYgKCJORUlHSEJPUkhPT0QiJWluJW5hbWVzKGRmKSkgdGFyZ2V0X2VuYyhkZiRORUlHSEJPUkhPT0QseWxvZyw3MCkgZWxzZSBOQQpkZiRCQ0xBU1NfVEUgPC0gaWYgKCJCVUlMRElORy5DTEFTUy5BVC5QUkVTRU5UIiVpbiVuYW1lcyhkZikpIHRhcmdldF9lbmMoc3Vic3RyKGRmJEJVSUxESU5HLkNMQVNTLkFULlBSRVNFTlQsMSwxKSx5bG9nLDYwKSBlbHNlIE5BCmRmJFRBWF9URSAgICA8LSBpZiAoIlRBWC5DTEFTUy5BVC5QUkVTRU5UIiVpbiVuYW1lcyhkZikpIHRhcmdldF9lbmMoZGYkVEFYLkNMQVNTLkFULlBSRVNFTlQseWxvZyw1MCkgZWxzZSBOQQoKZGYkQkNMQVNTX1IgPC0gaWYoIkJVSUxESU5HLkNMQVNTLkFULlBSRVNFTlQiJWluJW5hbWVzKGRmKSkKICAgICAgICAgICAgICAgICBhcy5udW1lcmljKHN1YnN0cihkZiRCVUlMRElORy5DTEFTUy5BVC5QUkVTRU5ULDEsMSk9PSJSIikgZWxzZSAwCmRmJFRBWF8yQyAgIDwtIGlmKCJUQVguQ0xBU1MuQVQuUFJFU0VOVCIlaW4lbmFtZXMoZGYpKQogICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoZGYkVEFYLkNMQVNTLkFULlBSRVNFTlQlaW4lYygiMkMiLCIwMkMiKSkgZWxzZSAwCmRmJFRBWF8yICAgIDwtIGlmKCJUQVguQ0xBU1MuQVQuUFJFU0VOVCIlaW4lbmFtZXMoZGYpKQogICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoZGYkVEFYLkNMQVNTLkFULlBSRVNFTlQlaW4lYygiMiIsIjAyIikpIGVsc2UgMAoKIyAtLS0tIE5FVyAxOiByZWxhdGl2ZSBzaXplIHdpdGhpbiBaSVAgKDEgZGYpIC0tLS0KaWYgKCFhbGwoaXMubmEoZGYkWklQKSkpIHsKICB6aXBfbWVkIDwtIHRhcHBseShkZiRMT0dfR1NGLCBkZiRaSVAsIGZ1bmN0aW9uKHYpIG1lZGlhbih2LCBuYS5ybT1UUlVFKSkKICBkZiRSRUxfU0laRV9aSVAgPC0gZGYkTE9HX0dTRiAtIGFzLm51bWVyaWMoemlwX21lZFtkZiRaSVBdKQogIGRmJFJFTF9TSVpFX1pJUFshaXMuZmluaXRlKGRmJFJFTF9TSVpFX1pJUCldIDwtIDAKfSBlbHNlIGRmJFJFTF9TSVpFX1pJUCA8LSAwCgojIC0tLS0gTkVXIDI6IHRpbWluZyDDlyBuZWlnaGJvcmhvb2QgVEUgKDEgZGYpIC0tLS0KZGYkVEl4TkUgPC0gd2l0aChkZiwgVElNRV9JTkRFWCAqIE5FSUdIX1RFKQoKIyAtLS0tIE5FVyAzOiB2ZXJ5LW9sZCBzdG9jayBoaW5nZSAoMSBkZjsgaGluZ2UgfjUweSBvbiBvcmlnaW5hbCBzY2FsZSkgLS0tLQpjdXRfYWdlIDwtIGxvZzFwKDUwKQpkZiRBR0VfSElOR0UgPC0gcG1heCgwLCBkZiRBR0VfTE9HIC0gY3V0X2FnZSkKCiMgbW9kZWwgZGF0YSAoa2VlcCDiiaUxM2spCmZlYXRzIDwtIGMoIkxPR19HU0YiLCJMT0dfTFNGIiwiQUdFX0xPRyIsIkFHRV9ISU5HRSIsIkZBUiIsIlRJTUVfSU5ERVgiLAogICAgICAgICAgICJORUlHSF9URSIsIkJDTEFTU19URSIsIlRBWF9URSIsIkJDTEFTU19SIiwiVEFYXzIiLCJUQVhfMkMiLAogICAgICAgICAgICJSRUxfU0laRV9aSVAiLCJUSXhORSIpCmRmbSA8LSBkZltjb21wbGV0ZS5jYXNlcyhkZlssYygiU0FMRS5QUklDRSIsZmVhdHMpXSksIGMoIlNBTEUuUFJJQ0UiLGZlYXRzKV0Kc3RvcGlmbm90KG5yb3coZGZtKSA+PSAxMzAwMCkKCiMgLS0tLSBmb3JtdWxhOiBrZWVwIHlvdXIgc3BpbmUgKyBwcmV2aW91cyBpbnRlcmFjdGlvbnMgKyAzIG5ldyB0ZXJtcyAtLS0tCnRlcm1zIDwtIGMoCiAgIm5zKExPR19HU0YsNCkiLCJucyhMT0dfTFNGLDIpIiwibnMoQUdFX0xPRywyKSIsIkFHRV9ISU5HRSIsIm5zKFRJTUVfSU5ERVgsMykiLCJGQVIiLAogICJORUlHSF9URSIsIkJDTEFTU19URSIsIlRBWF9URSIsIkJDTEFTU19SIiwiVEFYXzIiLCJUQVhfMkMiLAogICJSRUxfU0laRV9aSVAiLCJUSXhORSIsCiAgIkxPR19HU0Y6VEFYXzIiLCJMT0dfR1NGOkJDTEFTU19SIiwiRkFSOkxPR19HU0YiICAgIyBmcm9tIHlvdXIgc3Ryb25nIHZhcmlhbnQKKQpmb3JtX2Z1bGwgPC0gYXMuZm9ybXVsYShwYXN0ZSgibG9nKFNBTEUuUFJJQ0UpIH4iLCBwYXN0ZSh0ZXJtcywgY29sbGFwc2U9IiArICIpKSkKCiMgLS0tLSBrZWVwIOKJpDQwIGRmIChzaHJpbmsgc3BsaW5lcyBmaXJzdCwgdGhlbiBkcm9wIHdlYWtlc3QgbmV3IHRlcm1zIGlmIG5lZWRlZCkgLS0tLQpyZWR1Y2VfdG80MCA8LSBmdW5jdGlvbihmLGQpewogIGN1ciA8LSBmCiAgZHJvcF9vcmRlciA8LSBjKCJUSXhORSIsIlJFTF9TSVpFX1pJUCIsIkFHRV9ISU5HRSIsIkZBUjpMT0dfR1NGIiwiTE9HX0dTRjpUQVhfMiIsIkxPR19HU0Y6QkNMQVNTX1IiKQogIGZvciAoaXRlciBpbiAxOjEyMCl7CiAgICBrIDwtIHRyeShjb3VudF9kZihjdXIsZCksIHNpbGVudD1UUlVFKQogICAgaWYgKCFpbmhlcml0cyhrLCJ0cnktZXJyb3IiKSAmJiBrIDw9IDQwKSByZXR1cm4oY3VyKQogICAgIyBzdGVwIDE6IHJlZHVjZSBMT0dfR1NGIHNwbGluZSA0LT4zLT4yCiAgICBmc3RyIDwtIGRlcGFyc2UoY3VyKQogICAgaWYgKGdyZXBsKCJuc1xcKExPR19HU0YsNFxcKSIsIGZzdHIpKSB7IGZzdHIgPC0gZ3N1YigibnNcXChMT0dfR1NGLDRcXCkiLCJucyhMT0dfR1NGLDMpIiwgZnN0cikKICAgIH0gZWxzZSBpZiAoZ3JlcGwoIm5zXFwoTE9HX0dTRiwzXFwpIiwgZnN0cikpIHsgZnN0ciA8LSBnc3ViKCJuc1xcKExPR19HU0YsM1xcKSIsIm5zKExPR19HU0YsMikiLCBmc3RyKQogICAgfSBlbHNlIHsKICAgICAgIyBzdGVwIDI6IGRyb3AgbG93ZXN0LXByaW9yaXR5IHNpbmdsZS1kZiB0ZXJtcyBvbmUgYnkgb25lCiAgICAgIGZvciAodCBpbiBkcm9wX29yZGVyKSB7CiAgICAgICAgaWYgKGdyZXBsKHBhc3RlMCgiKF58XFwrfFxccykiLCBnc3ViKCJcXCsiLCJcXFxcKyIsdCksICIoJHxcXCt8XFxzKSIpLCBmc3RyKSkgewogICAgICAgICAgZnN0ciA8LSBzdWIocGFzdGUwKCJcXCtcXHMqIiwgdCksICIiLCBmc3RyKTsgYnJlYWsKICAgICAgICB9CiAgICAgIH0KICAgIH0KICAgIGN1ciA8LSBhcy5mb3JtdWxhKGZzdHIpCiAgfQogIGN1cgp9CmZvcm0gPC0gcmVkdWNlX3RvNDAoZm9ybV9mdWxsLCBkZm0pCgojIC0tLS0gZml0IHR3byB2YXJpYW50czogcm9idXN0IHZzLiBPTFMsIHBpY2sgbG93ZXIgUk1TRSAodGllIC0+IGhpZ2hlciBSXjIpIC0tLS0KZml0X2FuZF9ldmFsIDwtIGZ1bmN0aW9uKGRhdCwgZm9ybSwgcm9idXN0ID0gRkFMU0UsIGsgPSAxLjUpewogICMgYmFzZSBmaXQgKHVud2VpZ2h0ZWQpIHRvIGdldCByZXNpZHVhbCBzY2FsZQogIG0wIDwtIGxtKGZvcm0sIGRhdGEgPSBkYXQpCiAgaWYgKHJvYnVzdCl7CiAgICAjIHN0YW5kYXJkaXplZCByZXNpZHVhbHMKICAgIHNpZyA8LSBzdW1tYXJ5KG0wKSRzaWdtYQogICAgdSAgIDwtIHJlc2lkdWFscyhtMCkgLyBzaWcKCiAgICAjIEh1YmVyIHdlaWdodHM6IHcgPSBtaW4oMSwgay98dXwpCiAgICB3ICAgIDwtIHBtaW4oMSwgayAvIHBtYXgoMWUtOCwgYWJzKHUpKSkKICAgIHdbIWlzLmZpbml0ZSh3KV0gPC0gMQoKICAgICMgYXR0YWNoIHRvIGRhdGEgc28gbG0gY2FuIHNlZSBpdAogICAgZGF0JC5fX3cgPC0gdwogICAgbSA8LSBsbShmb3JtLCBkYXRhID0gZGF0LCB3ZWlnaHRzID0gLl9fdywgbmEuYWN0aW9uID0gbmEuZXhjbHVkZSkKICB9IGVsc2UgewogICAgbSA8LSBtMAogIH0KCiAgIyBwcmVkaWN0aW9ucyBvbiBsb2ctc2NhbGUsIHRoZW4gRHVhbiBzbWVhcmluZyB0byBnZXQgZG9sbGFycwogIHByZWRfbG9nIDwtIHByZWRpY3QobSwgbmV3ZGF0YSA9IGRhdCwgbmEuYWN0aW9uID0gbmEuZXhjbHVkZSkKICBzbWVhciAgICA8LSBtZWFuKGV4cChyZXNpZHVhbHMobSkpLCBuYS5ybSA9IFRSVUUpCiAgeWhhdCAgICAgPC0gZXhwKHByZWRfbG9nKSAqIHNtZWFyCiAgeSAgICAgICAgPC0gZGF0JFNBTEUuUFJJQ0UKCiAgcm1zZSAgPC0gc3FydChtZWFuKCh5IC0geWhhdCleMiwgbmEucm0gPSBUUlVFKSkKICBtYWUgICA8LSBtZWFuKGFicyh5IC0geWhhdCksIG5hLnJtID0gVFJVRSkKICByMiAgICA8LSAxIC0gc3VtKCh5IC0geWhhdCleMiwgbmEucm0gPSBUUlVFKSAvIHN1bSgoeSAtIG1lYW4oeSwgbmEucm0gPSBUUlVFKSleMiwgbmEucm0gPSBUUlVFKQogIGRmbW9kIDwtIG5jb2wobW9kZWwubWF0cml4KGZvcm0sIGRhdCkpIC0gMUwKCiAgbGlzdChtID0gbSwgc21lYXIgPSBzbWVhciwgcm1zZSA9IHJtc2UsIG1hZSA9IG1hZSwgcjIgPSByMiwKICAgICAgIGFkanIyID0gc3VtbWFyeShtKSRhZGouci5zcXVhcmVkLCBkZiA9IGRmbW9kKQp9CnJlc19vbHMgIDwtIGZpdF9hbmRfZXZhbChkZm0sIGZvcm0sIHJvYnVzdD1GQUxTRSkKcmVzX3JvYiAgPC0gZml0X2FuZF9ldmFsKGRmbSwgZm9ybSwgcm9idXN0PVRSVUUpCmJlc3QgPC0gaWYgKHJlc19yb2Ikcm1zZSArIDFlLTkgPCByZXNfb2xzJHJtc2UpIHJlc19yb2IgZWxzZSBpZiAoYWJzKHJlc19yb2Ikcm1zZS1yZXNfb2xzJHJtc2UpPDFlLTkgJiYgcmVzX3JvYiRyMj5yZXNfb2xzJHIyKSByZXNfcm9iIGVsc2UgcmVzX29scwoKY2F0KCJcbj09PT09PT09PT09IExBU1QtTUlMRSsrIFJFU1VMVFMgPT09PT09PT09PT1cbiIpCmNhdCgiRm9ybXVsYTpcbiAgIiwgZGVwYXJzZShmb3JtKSwgIlxuXG4iKQpjYXQoc3ByaW50ZigiUm93czogJWQgKOKJpTEzaykgfCBkZjogJWQgKOKJpDQwKVxuIiwgbnJvdyhkZm0pLCBiZXN0JGRmKSkKY2F0KHNwcmludGYoIkFkaiBSXjIgKGxvZyk6ICUuM2YgfCBEdWFuIHNtZWFyaW5nOiAlLjRmIHwgRml0OiAlc1xuIiwKICAgICAgICAgICAgYmVzdCRhZGpyMiwgYmVzdCRzbWVhciwgaWYgKGlkZW50aWNhbChiZXN0LHJlc19yb2IpKSAicm9idXN0IiBlbHNlICJPTFMiKSkKY2F0KHNwcmludGYoIiQtc2NhbGUgUk1TRTogJCVzIHwgTUFFOiAkJXMgfCBSXjI6ICUuM2ZcbiIsCiAgICAgICAgICAgIGZvcm1hdChyb3VuZChiZXN0JHJtc2UpLCBiaWcubWFyaz0iLCIpLCBmb3JtYXQocm91bmQoYmVzdCRtYWUpLCBiaWcubWFyaz0iLCIpLCBiZXN0JHIyKSkKCmBgYAoKYGBge3J9CiMgPT09PSBDT0VGRklDSUVOVCBUQUJMRSAmIElORkVSRU5DRSAoZm9yIHRoZSBjaG9zZW4gJ2Jlc3QkbScpID09PT0KCnByZXR0eV9zdGFycyA8LSBmdW5jdGlvbihwKXsKICBjdXQocCwgYnJlYWtzPWMoLUluZiwgLjAwMSwgLjAxLCAuMDUsIC4xLCBJbmYpLAogICAgICBsYWJlbHM9YygiKioqIiwiKioiLCIqIiwiLiIsIiAiKSwgcmlnaHQ9RkFMU0UpCn0KCnN1bW0gICA8LSBzdW1tYXJ5KGJlc3QkbSkKY29lZnQgIDwtIGFzLmRhdGEuZnJhbWUoc3VtbSRjb2VmZmljaWVudHMpCm5hbWVzKGNvZWZ0KSA8LSBjKCJFc3RpbWF0ZSIsIlN0ZC5FcnJvciIsInQudmFsdWUiLCJQcig+fHR8KSIpCmNvZWZ0JFNpZ25pZiA8LSBwcmV0dHlfc3RhcnMoY29lZnRbWyJQcig+fHR8KSJdXSkKCiMgOTUlIENJcyAoYmFzZS1SOyB3b3JrcyBmb3Igd2VpZ2h0ZWQgbG0gYXMgd2VsbCkKY2kgPC0gdHJ5KGNvbmZpbnQoYmVzdCRtKSwgc2lsZW50PVRSVUUpCmlmICghaW5oZXJpdHMoY2ksICJ0cnktZXJyb3IiKSkgewogIGNvZWZ0JENJXzIuNSAgPC0gY2lbcm93bmFtZXMoY29lZnQpLCAxXQogIGNvZWZ0JENJXzk3LjUgPC0gY2lbcm93bmFtZXMoY29lZnQpLCAyXQp9CgojIE9yZGVyIGJ5IGFic29sdXRlIHQKY29lZnQkQWJzX3QgPC0gYWJzKGNvZWZ0JGB0LnZhbHVlYCkKY29lZnQgPC0gY29lZnRbb3JkZXIoLWNvZWZ0JEFic190KSwgXQoKY2F0KCJcbj09PT09PT09PT09IENPRUZGSUNJRU5UIFRBQkxFIChzb3J0ZWQgYnkgfHR8KSA9PT09PT09PT09PVxuIikKcHJpbnRDb2VmbWF0KGFzLm1hdHJpeChjb2VmdFssIGMoIkVzdGltYXRlIiwiU3RkLkVycm9yIiwidC52YWx1ZSIsIlByKD58dHwpIildKSwKICAgICAgICAgICAgIFAudmFsdWVzPVRSVUUsIGhhcy5QdmFsdWU9VFJVRSwgc2lnbmlmLnN0YXJzPUZBTFNFLCBkaWdpdHM9NCkKY2F0KCJcblNpZ25pZi4gY29kZXM6ICAwIOKAmCoqKuKAmSAwLjAwMSDigJgqKuKAmSAwLjAxIOKAmCrigJkgMC4wNSDigJgu4oCZIDAuMSDigJgg4oCZIDFcbiIpCgojIEFkZCBzdGFycyAmIENJcyBpbiBhIHRpZHkgdmlldwpjYXQoIlxuVG9wIDI1IGJ5IHx0fCAod2l0aCBzdGFycyAmIDk1JSBDSSk6XG4iKQpwcmludCh1dGlsczo6aGVhZChjb2VmdFssIGMoIkVzdGltYXRlIiwiU3RkLkVycm9yIiwidC52YWx1ZSIsIlByKD58dHwpIiwiU2lnbmlmIiwiQ0lfMi41IiwiQ0lfOTcuNSIpXSwgMjUpLCBkaWdpdHM9NCkKCiMgTW9kZWwgb3ZlcnZpZXcgYWdhaW4gZm9yIGNvbnZlbmllbmNlCmNhdCgiXG49PT09PT09PT09PSBNT0RFTCBPVkVSVklFVyA9PT09PT09PT09PVxuIikKY2F0KHNwcmludGYoIk9ic2VydmF0aW9uczogJWRcbiIsIG5vYnMoYmVzdCRtKSkpCmNhdChzcHJpbnRmKCJNb2RlbCBkZiAoZXhjbC4gaW50ZXJjZXB0KTogJWRcbiIsIGJlc3QkZGYpKQpjYXQoc3ByaW50ZigiQWRqIFJeMiAobG9nKTogJS4zZiB8IER1YW4gc21lYXJpbmc6ICUuNGYgfCBGaXQ6ICVzXG4iLAogICAgICAgICAgICBiZXN0JGFkanIyLCBiZXN0JHNtZWFyLCBpZiAoaWRlbnRpY2FsKGJlc3Qkcm1zZSwgcmVzX3JvYiRybXNlKSAmJiBpZGVudGljYWwoYmVzdCRyMiwgcmVzX3JvYiRyMikpICJyb2J1c3QiIGVsc2UgIk9MUyIpKQpjYXQoc3ByaW50ZigiJC1zY2FsZSBSTVNFOiAkJXMgfCBNQUU6ICQlcyB8IFJeMjogJS4zZlxuIiwKICAgICAgICAgICAgZm9ybWF0KHJvdW5kKGJlc3Qkcm1zZSksIGJpZy5tYXJrPSIsIiksIGZvcm1hdChyb3VuZChiZXN0JG1hZSksIGJpZy5tYXJrPSIsIiksIGJlc3QkcjIpKQoKIyBPcHRpb25hbDogd3JpdGUgdG8gQ1NWIGZvciB0aGUgd3JpdGV1cApvdXRfdGFiIDwtIGNvZWZ0WywgYygiRXN0aW1hdGUiLCJTdGQuRXJyb3IiLCJ0LnZhbHVlIiwiUHIoPnx0fCkiLCJTaWduaWYiLCJDSV8yLjUiLCJDSV85Ny41IiwiQWJzX3QiKV0KdXRpbHM6OndyaXRlLmNzdihvdXRfdGFiLCAibGFzdF9taWxlX2NvZWZmaWNpZW50cy5jc3YiLCByb3cubmFtZXM9VFJVRSkKY2F0KCJcblNhdmVkIGNvZWZmaWNpZW50cyB0bzogbGFzdF9taWxlX2NvZWZmaWNpZW50cy5jc3ZcbiIpCmBgYA==