# ----------------------------------------------------------------------
# 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==