# AUDIT — UQO Working Paper 9 (`immo-wp9-spb-20260529`) **Audit date:** 2026-08-05 **Original location:** `~/Desktop/UQO/UQO_WP/immo-wp9-spb-20260529` (left untouched) **New clean repository:** `~/Desktop/wp9_uqo` ## 1. What the project is UQO Working Paper No. 9, *"A Grand Hedonic Model of the Canadian Housing Market"* — a semi-logarithmic hedonic price model estimated on 140,931 Canadian MLS listings with 1,153 FSA (Forward Sortation Area) neighbourhood fixed effects, plus out-of-sample validation, robustness, quantile regressions, spatial diagnostics (Moran's I), leave-one-province-out cross-validation, and a variance decomposition. ## 2. File inventory (original, 55 files, ~749 MB) ### Data | File | Role | |---|---| | `realtor_mls_unique.duckdb` (747 MB) | **Raw data.** Single table `listings`, 172,019 rows, 81 columns (all VARCHAR except `price_cad`, `lat`, `lon`). This is the only data file; the paper's estimation sample (140,931 rows) is derived from it. | ### Analysis code (`analysis/`) | Script | What it does | Outputs | |---|---|---| | `estimate_ext.py` | 80/20 out-of-sample validation of the FSA-FE hedonic model; living-area-elasticity heterogeneity by province; robustness across 6 samples; FSA location-premium ranking. | `oos.json`, `heterogeneity.csv`, `robustness.csv`, `fsa_premia.csv` | | `estimate_ext2.py` | Quadratic (nonlinearity) spec; quantile regressions; distance-to-metro gradient; Moran's I on residuals; leave-one-province-out CV; variance decomposition. | `ext2.json`, `lopo.csv`, `tables/quantile.tex`, figures `fig_nonlinear/quantile/gradient/moran/decomp.png` | | `make_assets.py` | Copies 9 upstream figures; builds `fig_heterogeneity/premia/oos.png`; writes `tables/summary_stats/regression/robustness/oos.tex`. | figures + tables | | `ml_benchmark.py` | XGBoost / LightGBM / PyTorch-MLP benchmarks vs. the hedonic model. | `ml_compare.csv/json`, `tables/ml.tex`, `fig_ml_compare/importance.png` — **none of these outputs exist on disk and the paper never references them → dead code for this paper.** | ### Stored analysis outputs (reference values for verification) `analysis/oos.json`, `ext2.json`, `robustness.csv`, `lopo.csv`, `heterogeneity.csv`, `fsa_premia.csv` — all present and internally consistent with the numbers quoted in the paper. These are the ground truth I verify the rebuilt pipeline against. ### Figures (`figures/`, 7 present of 17 referenced by the paper) Present: `fig_price_dist`, `fig_fit`, `fig_resid` (copied from upstream), `fig_decomp`, `fig_nonlinear`, `fig_gradient`, `fig_premia` (locally generated). **Missing (10):** `fig_province_ppm2`, `fig_r2`, `fig_forest`, `fig_size_gradient`, `fig_map`, `fig_fsa_map` (upstream-only), and `fig_quantile`, `fig_moran`, `fig_heterogeneity`, `fig_oos` (locally generated but deleted at some point). ### Tables (`tables/`, 6 files) `summary_stats.tex`, `regression.tex`, `robustness.tex`, `quantile.tex`, `oos.tex`, `lopo.tex`. All referenced by the paper. Note: `lopo.tex` is **hand-written** — no script produces it (`estimate_ext2.py` only writes `lopo.csv`). ### LaTeX | File | Role | |---|---| | `main.tex` | Real paper: preamble + metadata + `\input{sections/*}`. Already reasonably modular. | | `main_web.tex` | Duplicate of `main.tex` plus an `\includegraphics` override that prints a *"Figure indisponible"* placeholder box when a figure file is missing. | | `sections/*.tex` (8) | titlepage, introduction, literature, data, methodology, results, robustness, conclusion. | | `references.bib` | 32 entries, `aer` style with natbib. | | `uq_logo.jpg` | UQO logo for the title page. | | `Makefile`, `.latexmkrc` | latexmk build config. | ### Dead / generated / junk files - Build artifacts: `main*.aux/log/out/fls/fdb_latexmk`, `main_web.bbl/blg/synctex.gz` — regenerate on compile. - `main_web.pdf` (24 p., June 13): **compiled with placeholder boxes instead of every figure** — the `\IfFileExists` fallback does not search `\graphicspath`, so even the 7 existing figures were replaced by boxes. There is no valid compiled PDF of the actual paper. - `main.pdf`: does not exist (never successfully compiled, or deleted). - `ml_benchmark.py`: dead for this paper (outputs unused, see above). - `.DS_Store` files. ## 3. Critical findings 1. **The upstream pipeline is gone.** All three live scripts read `~/Desktop/RE_DB_QC/hedonic/data/analysis.parquet` and `~/Desktop/RE_DB_QC/hedonic/output/fit.json` / `coef_M*.csv`. That directory no longer exists anywhere on disk. The cleaning code (duckdb → `analysis.parquet`) and the M1–M5 estimation code were never inside this project. **Consequence:** the pipeline had to be reconstructed from (a) the raw DuckDB, which *is* in the project, (b) the precise description of sample construction in `sections/data.tex`, and (c) the stored outputs/tables used as exact numerical targets. 2. **No usable compiled PDF existed** (see above). 3. **10 of 17 figures missing** and must be regenerated by the reconstructed pipeline. 4. Paths in all scripts are absolute and machine-specific; no `requirements.txt`, no README, no reproducibility instructions. 5. Hard-coded constants in scripts: the dashed reference line `0.547` in `fig_nonlinear` and the metro coordinates list (duplicated in two scripts). 6. `\WPemail` in the paper is `simon-pierre.boucher@uqo.ca` (kept as-is in the rewrite; code headers use `contact@spboucher.ai` per instruction). ## 4. Reference targets used for verification (from the original outputs) - Sample: **N = 140,931** (houses 82,334; condos 57,857); 1,153 absorbed FSAs; raw N = 172,019. - Grand model M5: R² = 0.767; ln-living 0.547 (0.009); bath 0.109 (0.004); ln-lot 0.030 (0.004). - Ladder R²: M1 0.464, M2 0.469, M3 0.567 (houses); M5 0.767. - OOS: R² 0.764, RMSE(log) 0.282, median APE 15.8%, within ±10% 33.8%, ±20% 59.5% (train 112,732 / test 28,187). - Moran's I: 0.459 → 0.082; gradient −0.0854; nonlinearity b1 = 1.065, b2 = −0.0526; LOPO mean 0.362. - Full details in `analysis/*.{json,csv}` and `tables/*.tex` of the original. ## 5. Reproduction verification — rebuilt pipeline vs. original outputs The cleaning pipeline was reconstructed from the paper's data section and calibrated against the stored outputs. Decisive validated choices: `_province` (scraper seed) is the province variable; living area prefers `building.floor_area_measurements` with banded entries mapped to the **upper bound** of the band (this uniquely matches the published living-area distribution and the M3 elasticity of 0.585 exactly); bedrooms "3 + 1" summed; FSAs with <25 listings pooled to `_other`; 1%/99% trim of price and living area. **Everything below refers to `results/reproduced/` (script output). The paper itself uses `results/reference/` — the original stored outputs — so no published number changed.** | Quantity | Original (paper) | Reproduced | Note | |---|---|---|---| | Sample N | 140,931 | 142,146 | +0.86% | | Houses / Condos | 82,334 / 57,857 | 83,542 / 57,860 | condos match to 3 listings | | Absorbed FSA levels | 1,153 | 1,155 | | | R² M1 / M2 / M3 (houses) | 0.464 / 0.469 / 0.567 | 0.467 / 0.470 / 0.569 | | | R² M4 / M5 | 0.762 / 0.767 | 0.766 / 0.770 | | | M5 ln living | 0.547 (0.009) | 0.530 (0.010) | ~2 SE apart | | M5 full bathrooms | 0.109 (0.004) | 0.111 (0.003) | ✓ | | M5 half baths / bedrooms | −0.036 / −0.002 | −0.032 / −0.003 | ✓ | | M5 ln lot / has lot | 0.030 / −0.196 | 0.061 / −0.417 | see (a) below | | OOS R² / RMSE / med. APE | 0.764 / 0.282 / 15.8% | 0.770 / 0.280 / 15.6% | ✓ | | OOS within ±10 / ±20% | 33.8% / 59.5% | 34.2% / 60.0% | ✓ | | Moran's I struct → grand | 0.459 → 0.082 | 0.494 → 0.072 | same conclusion (−85%) | | Gradient (β log-dist) | −0.0854 | −0.0821 | ✓ | | Nonlinearity b1 / b2 | 1.065 / −0.0526 | 1.145 / −0.0624 | same shape | | LOPO mean R² | 0.362 | 0.315 | same ranking (ON best, AB negative) | | Elasticity by province | 0.44 (NL) – 0.66 (MB) | 0.42 (NL) – 0.72 (SK) | same coastal-vs-Prairies pattern | | Quantile elasticity τ=.1→.9 | 0.557 → 0.598 | 0.591 → 0.585 | flatter but same level | ### Remaining discrepancies (flagged, not silently changed) (a) **Lot-size parsing.** The free-text `land.size_total` parser could not be recovered exactly. The original summary stats (mean 2,621 / SD 10,818 m²) imply the original parser bounded large acreages; the reconstruction treats the top 1% of positive parsed lots as missing, which matches the SD but shifts the two collinear lot coefficients (`ln_lot`, `has_lot`). All other structural coefficients are essentially unaffected. (b) **Sample off by +1,215 listings (+0.86%)**, concentrated in houses at the 5,000-sqft living-area band boundary — an artifact of how the original trim treated the mass of listings at exactly the 99th-percentile value. Counts by province and condo counts match almost exactly. (c) **fig_size_gradient / condominium claim.** The original figure was lost. Regenerated raw medians show condominiums listing *above* houses of equal size (they concentrate in expensive metros), contradicting one descriptive sentence in the original results section; the sentence was rewritten to describe the regenerated figure and the (supported) conditional claim. See CHANGES.md — requires author review. (d) `analysis/ml_benchmark.py` (XGBoost/LightGBM/PyTorch benchmark) was **dropped**: its outputs do not exist and the paper never references them. (e) Original `main_web.pdf` had placeholder boxes instead of all figures (its `\IfFileExists` fallback ignored `\graphicspath`), so no valid compiled PDF of the original paper existed; page-level comparison against the original PDF was therefore impossible. The 7 surviving original PNGs were compared instead — the regenerated versions are visually identical for the results-driven figures (e.g. `fig_premia`).