assessment-model/docs/adr/0007-postcode-search-creates-properties.md
Khalim Conn-Kowlessar fac425762e fix(postcode-search): classify RH (HMO) family as addable residential
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>
2026-07-09 10:33:07 +00:00

64 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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, RH01/RH02/RH03→House (the dedicated HMO family, built
form unknowable). 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.
- **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_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.