mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-19 17:02:59 +00:00
7 Leyton Avenue (UPRN 100031252876) came back from OS Places as RH03
("HMO Not Further Divided") and rendered as "Non-residential", so it
couldn't be added from the postcode-search "add properties" journey.
classifyOsCode decided "residential?" purely on an RD prefix, dropping the
whole RH (HMO) family — even though RD07 (the RD-family HMO code) was
already mapped to House. Extend the residential check to RH and map
RH01/RH02/RH03 to House (built form unknowable), consistent with RD07.
Updates ADR-0007's enumerated mapping table to stay truthful.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3.2 KiB
3.2 KiB
7. Postcode search creates Properties directly, with OS-derived overrides
Date: 2026-07-06
Status
Accepted
Numbering note: 0003 is taken by the app-authored-scenarios ADR (PR #353);
0004–0006 are referenced by CONTEXT.md but were lost to a docs/adr/**
.gitignore rule (removed in this commit). 0007 avoids all collisions.
Context
Properties previously came into existence only via the BulkUpload finaliser or the backend pipeline. The "Postcode search" journey (CONTEXT.md) lets a user search a postcode (postcodes.io for validation + geography, OS Places for addresses) and add selected addresses to a Portfolio directly — before any modelling exists.
Decision
- Next.js inserts
propertyrows directly: address, postcode, uprn, per-property lat/lng, local_authority (postcodes.ioadmin_district), constituency (postcodes.ioparliamentary_constituency). Nothing inferred — no type/age/tenure guesses on the row. Energy state is derived: no EPC-graph rows ⇒ "awaiting modelling" (mirrors ADR on app-authored scenarios). - Dedup rides the existing partial unique index
uq_property_portfolio_uprnviaON CONFLICT DO NOTHING; the submit reports added vs already-existed. - Both override layers are written, mimicking the finaliser: a
VocabularyMapping upsert per distinct OS classification code and a
property_overridessnapshot per property (original description = the OS code). Newoverride_sourceenum valueos_places— the deterministic OS-code mapping is not the LLM "classifier" and provenance must stay distinguishable. - Deterministic mapping (fixed table, tested): RD02→House+Detached,
RD03→House+Semi-Detached, RD04→House (Mid/End unknowable — no built form),
RD06→Flat, RD07→House, RH01/RH02/RH03→House (the dedicated HMO family, built
form unknowable). Every other code maps nothing;
Unknownis never written (absence, not noise) — preserving the finaliser's invariant. The backend's predict-EPC service consumes these for archetype matching. - Addable codes: the RD (dwelling) family except RD10 (institutions), plus the RH (HMO) family. All other codes — commercial, institutional, land — are not addable and render "Non-residential".
- Read-through cache on the existing
postcode_searchjsonb table: serve when fresher than 30 days, else refresh from OS Places and upsert.
Alternatives considered
- Routing through the BulkUpload pipeline (synthesise a one-postcode "upload"): full provenance for free, but drags an interactive journey through an async multi-stage state machine built for files.
- Property row carries property_type directly: bypasses the override layers the Model backend actually consumes, and creates a second source of truth.
Consequences
- A third Property creator exists; anything assuming "property ⇒ came from a BulkUpload or the backend" is now wrong.
property_overridesgains a second writer withos_placesprovenance; re-classification tooling must not clobber user rows (existing rule) nor treat OS rows as user-authored.- Postcode-search-created properties render blank/awaiting-modelling in the portfolio table and reporting until a scenario is modelled against them.