assessment-model/docs/adr/0007-postcode-search-creates-properties.md
Khalim Conn-Kowlessar b86efddfdf chore(adr): stop gitignoring docs/adr; add ADR-0007 for postcode-search property creation
The docs/adr/** ignore rule silently ate ADRs 0004-0006 that CONTEXT.md
still references. Remove it so decisions actually land in the repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:07:43 +00:00

2.9 KiB
Raw Blame History

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); 00040006 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 property rows directly: address, postcode, uprn, per-property lat/lng, local_authority (postcodes.io admin_district), constituency (postcodes.io parliamentary_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_uprn via ON 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_overrides snapshot per property (original description = the OS code). New override_source enum value os_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. Every other code maps nothing; Unknown is never written (absence, not noise) — preserving the finaliser's invariant. The backend's predict-EPC service consumes these for archetype matching.
  • Read-through cache on the existing postcode_search jsonb 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_overrides gains a second writer with os_places provenance; 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.