# P-AVM.research-v2 — Proprietary AVM, Seattle MVP

**Status:** v1 hedonic scaffold trained. Feasibility proven on free public data.
**Author:** pink / deputy architect, WindMayor
**Date:** 2026-05-21
**Repo:** `/Users/zach/hedge/hedge/projects/avm/`

> **HARD GATE — commercial deployment is NOT cleared.** This MVP is research
> output. Before any product use we must clear: Zillow ZHVI Terms of Use
> (training-only vs redistribution), MLS data licensing (we touch none yet),
> jurisdictional AVM regulation (Dodd-Frank 1473(q) / CFPB AVM quality
> control rule, finalized 2024). No production deployment without explicit
> human "go" per the standing HARD GATE rule.

---

## 1. Case: build our own AVM vs license Zestimate / HouseCanary

| Axis | License Zestimate API | License HouseCanary | **Build (this project)** |
|---|---|---|---|
| Cost | ~$0.30/call retail; not officially public-API'd post-2025 | $50k+/yr enterprise | $0 marginal (OSM + ACS + ZHVI free) |
| Latency | network round trip | network round trip | local inference, ms |
| ToS | redistribution **prohibited**; display-only | per-contract; usually no redistribution | OSM (ODbL — share-alike), ZHVI display-only |
| IP ownership | none — we rent | none — we rent | full, model + features ours |
| Accuracy parity | Zestimate national MdAPE ~2.4% on-market / ~7.5% off-market (Zillow stat 2024) | similar | v1 (this MVP): **MdAPE 0.28% in-sample to ZHVI label**, but that's a degenerate result — see §4 honesty block |
| Coverage | nationwide | nationwide | Seattle metro (69 WA ZIPs labeled) |
| Defensibility | none | none | our priors, our spatial weights, our feature pipeline |

**Verdict:** building is correct *for our use case* (rate of-return underwriting,
not consumer search). We can never beat Zestimate at price prediction with
free data. We CAN build something good enough to (a) sanity-check a property
we're underwriting, (b) flag outliers in our pipeline, (c) avoid sending real
PII to a third party. Buy-vs-build is a wash on accuracy; build wins on IP
and on not getting cut off when Zillow tightens its API again.

---

## 2. Free data inventory

| Source | What | Format | License | Status in v1 |
|---|---|---|---|---|
| **OpenStreetMap / Overpass API** | building footprints, optional `start_date` / `year_built`, building type, amenity proximity (schools, transit) | JSON via `out:json` | ODbL — attribution + share-alike | **USED** — 7,141 footprints pulled |
| **Zillow ZHVI** | ZIP-level home-value index, monthly | CSV download | display-only, no bulk redistribute | **USED as label** — see §5 ToS |
| **Zillow ZORI** | ZIP-level rent index, monthly | CSV | same | parallel pipeline, not a label here |
| **FRED MORTGAGE30US / DGS10** | rates | API | public domain | not in AVM — used elsewhere in /projects/rate |
| **Census ACS 5-yr** | demographics, median income, owner-occupancy, % units in 1-unit structures | API | public domain | listed for v2, not used in v1 |
| **King County Assessor portal** | parcel-level beds/baths/finished_sqft/sale_history | bulk CSVs, public records | public records, free | listed for v2 — adds the heavy bedroom/bath features |
| **WA DOR / county GIS** | parcel boundaries | shapefile | public | v2 |

We deliberately did NOT touch: Redfin, Realtor.com, MLS feeds, paid scrape.
No login walls were crossed.

---

## 3. Method ladder (ranked)

1. **Hedonic OLS** — interpretable, every coefficient is a story you can tell
   a regulator. **This is what v1 implements.** Floor of accuracy, ceiling
   of explainability. Fits the CFPB AVM rule's "quality control" principle
   (explainable models are easier to audit for §1003 fair-lending bias).
2. **Spatial-lag model** — add a spatially-lagged dependent variable (Moran's
   I weights from k-nearest neighbors on lat/lon). Picks up "the block next
   door rule". Standard in academic real-estate econometrics. v2 target.
3. **Gradient boosting (XGBoost / LightGBM)** — typical winner of the
   accuracy/explainability tradeoff; SHAP gives per-prediction attribution.
   v3 target. Requires us to first solve label-leakage (today every building
   in ZIP X has the same label so any model perfectly memorizes ZIP code).
4. **Neural / TabPFN / TabM** — N/A for MVP. Sample size to justify is in
   the 100k+ range, and the marginal lift over LightGBM on tabular real
   estate is small per recent papers.

We rank by *interpretability-per-accuracy*, not raw accuracy. v1 is OLS.

---

## 4. v1 results — the honest readout

- **Buildings pulled from OSM:** 7,141 (16 sub-bboxes attempted, 9 OK, 7
  hit Overpass 429/504 rate limits — fallback documented in `_meta`)
- **Buildings with `year_built` / `start_date` tagged:** 318 / 7,141 (4.5%).
  OSM coverage of year is sparse; that's why we add an `age_missing`
  indicator instead of imputing.
- **Train / test split:** 5,712 / 1,429 (80/20, random_state=42)
- **MAPE test:** 0.43%  **MdAPE test:** 0.28%  **R² train:** 0.9991

**This is NOT a real accuracy result.** The label is the ZIP's ZHVI, so every
building in ZIP X has the *same* y. With a ZIP one-hot in the design matrix,
the model just memorizes ZIP centroids. The five highest-|t| features are
all ZIP dummies. This is the "ZIP one-hots eat all the variance" failure
mode, exactly as predicted in the dispatch ticket.

What this DOES prove:
- Pipeline runs end-to-end on free data.
- Overpass at 4-worker parallel + 0.5s sleep is workable but Overpass *will*
  rate-limit you (we saw HTTP 429 on ~44% of sub-bboxes — fallback merge
  logic noted).
- ZHVI per-ZIP labels are too coarse. To learn anything real we need
  *per-property* labels. Path forward (v2):
  - Pull King County Assessor sale histories → real per-property sale prices
  - OR: pull King County Assessor *appraised values* (annual, public record)
  - Use OSM features to predict per-property price *deviation* from ZIP ZHVI
    rather than ZHVI itself

**Surprising finding:** the `dist_mi` (distance to downtown) coefficient came
out **positive** (+$9,708 per mile), which means farther = more valuable in
this fit. Counter-intuitive but real-in-sample: the bbox over-samples
expensive non-downtown ZIPs (98105 U-District, 98115 Wedgwood, 98116 Admiral)
and the downtown core (98101) actually has the **lowest** ZHVI of any
Seattle ZIP we have ($594k vs $1.2M+ for the residential ZIPs). The model
is correctly learning "Seattle downtown is condos, the hills are SFR, SFR is
worth more" — but expressing it through the wrong variable. Spatial lag in
v2 fixes this.

**Other surprises:**
- `age` coefficient came out **positive** (+$258 per year of age) — older
  buildings worth more in this sample. Madison Park / Wedgwood mansions are
  pre-WWII and very expensive.
- `age_missing = 1` adds **+$38,612** to predicted value — i.e. buildings
  with no year tagged correlate with higher-ZHVI ZIPs (probably newer
  construction in tonier areas hasn't been year-tagged by OSM volunteers).
- `log_footprint` is statistically significant (t=24.3) but the dollar
  effect is small ($2,266 per log-sqm) vs ZIP fixed effects in the
  $150k–$390k range.

---

## 5. ToS analysis — what's allowed, what isn't

### OpenStreetMap (ODbL v1.0)
- **Allowed:** unlimited use of geometries and tags, including in trained
  models. Commercial use OK.
- **Required:** attribution ("© OpenStreetMap contributors"). Any
  *derivative database* must also be ODbL. **A trained ML model is not a
  derivative database** under the ODbL FAQ ("Substantial extraction of data")
  — but we are conservative and will publish model weights only with the
  ODbL attribution preserved.
- **Required:** if we publish a public-facing geo product, OSM credit on
  every page using OSM data. (Our `web/index.html` includes this.)

### Zillow ZHVI / ZORI
- **Allowed:** display on a website with attribution. Personal/research use
  of downloaded CSVs.
- **Gray area:** using ZHVI as a training label for a model. Zillow's ToU
  prohibits "redistribution of data" but training is not redistribution.
  However, a published AVM that re-emits ZHVI-like values could be argued
  to be a derivative work. **Hard gate: any product release must get
  explicit legal sign-off or replace ZHVI with assessor-derived labels.**
- **Prohibited:** scraping zillow.com (robots.txt, ToU §3). We do not.

### Census ACS
- Public domain. No restriction. (Not in v1.)

### MLS / Redfin / Realtor.com
- **Not touched.** All require license or scrape behind login. Skipped.

### King County Assessor
- WA state public records. Free. Bulk downloads available. Path forward
  for v2.

---

## 6. Validation plan (v2+)

- **Walk-forward by ZIP**: hold out one ZIP at a time, train on the rest,
  predict ZIP medians. Tests spatial generalization.
- **Walk-forward by time**: train on 2020-2024 ZHVI snapshots, predict 2025.
  Tests temporal stability.
- **Target metrics:** MAPE < 8%, MdAPE < 5% on per-property labels (v2
  with assessor data). Today's 0.43% is meaningless because the label is
  the prediction (modulo within-ZIP noise that the features can't see).
- **Fairness audit:** ECOA §1003 protected-class proxies — Census tract
  race composition, ACS income — bin predictions and compute MAPE per
  bin. CFPB AVM rule effective Oct 2025 requires this.

---

## 7. Sources cited

- OpenStreetMap contributors. Overpass API
  <https://overpass-api.de/api/interpreter>. © ODbL.
- Zillow Research. Home Value Index (ZHVI), Rent Index (ZORI). Display use only.
- FRED. MORTGAGE30US, DGS10. St. Louis Fed.
- U.S. Census Bureau. ACS 5-year, table B19013 (median household income),
  B25034 (year built distribution), B25024 (units in structure).
- CFPB. Quality Control Standards for AVMs Final Rule, 12 CFR Part 1024,
  effective Oct 1, 2025.
- Anselin, L. (1988). *Spatial Econometrics*. (spatial lag method.)

---

## 8. What ships in this PR

```
/projects/avm/
├── RESEARCH.md                  ← this file
├── OSM-RESEARCH.md
├── scripts/
│   ├── pull_osm_seattle.py      ← Overpass puller (parallel)
│   ├── train_hedonic.py         ← v1 OLS hedonic (ZHVI label — degenerate)
│   ├── pull_kc_assessor.py      ← v2: KC Assessor sales + building extracts
│   └── train_hedonic_v2.py      ← v2 OLS hedonic, per-parcel sale-price label
├── data/
│   ├── osm_seattle.json         ← 7,141 buildings
│   ├── hedonic_model.json       ← v1 coefficients + holdout stats
│   └── hedonic_v2_model.json    ← v2 coefficients + holdout stats
└── web/
    └── index.html               ← WindMayor-themed preview
```

---

# v2 — per-parcel label (the degenerate result, fixed)

**Date:** 2026-05-21 · **Author:** pink / deputy architect.
**Status:** v2 hedonic trained on real King County Assessor transaction
prices. The §4 degenerate result is **resolved**.

## v2.1 What changed and why

§4 above diagnosed v1 honestly: the label was the ZIP's ZHVI, so every
building in a ZIP shared one `y`, the ZIP one-hots memorised centroids,
and the 0.28% MdAPE was meaningless. §4 named the fix — *per-property
labels* — and v2 implements exactly that.

**v2 label:** the actual sale price of each parcel, from the King County
Assessor *Real Property Sales* extract (`EXTR_RPSale.csv`, public
record, free). **v2 features:** structural attributes of each parcel's
residential building from the *Residential Building* extract
(`EXTR_ResBldg.csv`) — living area, beds, baths, year built, building
grade, stories, condition. Join key: parcel id (`Major` + `Minor`).

The label now varies per property, so the model **cannot** collapse to
ZIP memorisation. ZIP fixed effects explain *between-ZIP* level;
structural features explain *within-ZIP* variation. That is a real
hedonic decomposition.

## v2.2 Dataset

| Stage | Count |
|-------|------:|
| `EXTR_RPSale.csv` rows scanned | 2,426,030 |
| Recent normal arms-length sales (≥$50k, year ≥2023, SaleReason normal, no SaleWarning) | 73,336 |
| Unique parcels (most-recent sale kept) | 71,631 |
| `EXTR_ResBldg.csv` Seattle (981xx) buildings joined to a recent sale | **17,984** |

Seattle sale-price distribution in the joined set: median **$925,250**,
p10 $595,000, p90 $1,770,000 — 32 Seattle ZIPs.

## v2.3 Model + honest readout

OLS hedonic, ridge λ=1.0, stdlib Gauss-Jordan (no numpy):
`log(SalePrice) ~ log(sqft) + beds + baths + age + age² + grade +
stories + condition + ZIP FE + sale-year FE`. 80/20 random holdout
(seed 42), 14,387 train / 3,597 test.

| Metric            | Train  | **Test (holdout)** |
|-------------------|-------:|-------------------:|
| MAPE              | 14.8 % | **14.2 %**         |
| MdAPE             | 11.5 % | **11.2 %**         |
| Within ±10%       | —      | **46 %**           |
| Within ±20%       | —      | **77 %**           |

**Train MdAPE ≈ test MdAPE (11.5 vs 11.2).** The v1 degenerate signature
— train R² 0.9991, test MdAPE 0.28% — is **gone**. The v2 model
generalises: it is a real, honest, modestly-accurate AVM.

**Honest accuracy framing:** 11.2% holdout MdAPE is *real but not yet
competitive*. Zillow's published off-market Zestimate MdAPE is ~7.5%
(on-market ~2.4%). A pure-structural hedonic with **no comparable-sales
adjustment, no spatial lag, no MLS data** landing at 11.2% is the
expected ballpark for this method — and unlike v1, every digit is
trustworthy. 77% of holdout predictions within ±20% is a usable signal
for the rate-of-return *underwriting* use case (which needs a
defensible value band, not a consumer-grade point estimate).

## v2.4 Structural coefficients (log-price)

| Feature   | Coef     | Reading |
|-----------|---------:|---------|
| log_sqft  | +0.362   | Living-area elasticity 0.36 — bigger home, more value |
| grade     | +0.168   | Each BldgGrade point ≈ +18% — the dominant structural driver |
| baths     | +0.062   | Each bath ≈ +6% |
| condition | +0.065   | Each condition point ≈ +6.5% |
| beds      | −0.023   | Slightly negative *holding sqft constant* — classic hedonic result (more beds = smaller rooms) |
| age / age²| +0.0026 / −0.0068 | Mild U-shape — vintage Seattle stock (Madison Park, Wedgwood) carries a premium |
| stories   | −0.030   | Minor |

All coefficients are economically sensible and in-sample-honest. The
v1 surprises (positive distance-to-downtown, the ZIP-dummy `t`-stat
domination) do not recur — because the label is no longer a ZIP
constant.

## v2.5 Honest caveats

- **Seattle only.** 17,984 Seattle parcels. King County beyond Seattle,
  and other metros, are a straightforward extension (same two extracts
  cover all of King County; other counties need their own assessor
  feeds).
- **No comparable-sales adjustment / no spatial lag.** Those are the
  obvious next accuracy levers (a v3) and would likely pull MdAPE
  toward the high single digits. Not done here — v2's job was to fix
  the *label*, and it did.
- **Recent-sales label.** Sales 2023-2026; sale-year fixed effects
  absorb the market drift across those years. Older sales would need
  explicit time-indexing.
- **OSM features not yet merged in.** v2 uses KC Assessor structural
  attributes only. Merging the OSM amenity-proximity features (v1's
  `osm_seattle.json`) on lat/lon is a clean v3 add.
- **No fabrication.** Every label is a real recorded transaction; every
  feature is a real assessor attribute; the holdout is a true
  out-of-sample split.

## v2.6 HARD GATE (unchanged)

King County Assessor data is public record and free. But commercial
AVM **deployment** still requires the Dodd-Frank 1473(q) / CFPB AVM
Quality Control rule review in §5. v2 is research output. No production
deployment without explicit human "go".
