assessment-model/docs/wip/postcode-search-handover.md
Khalim Conn-Kowlessar 302e200ed6 docs: handover for postcode-search build continuation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:38:54 +00:00

3.6 KiB
Raw Blame History

Handover: Add properties by postcode search

Branch: feature/add-properties-by-postcode (2 commits pushed, tree clean). Context date: 2026-07-06. Goal journey: add properties → create scenarios (done, PR #353) → trigger modelling (soon).

State

Done and verified (npx tsc --noEmit clean, 329 tests pass):

  • Domain core src/lib/postcodeSearch/model.ts + model.test.ts (9 tests): normalisePostcode, classifyOsCode, isCacheFresh (30-day), toAddressCandidates (DPA>LPI, UPRN-dedup, postcode-stripped title-case address), buildWritePlan (property row + 02 override facts).
  • ADR-0007 (docs/adr/) — all decisions + rationale. docs/adr/** gitignore rule removed (it had eaten ADRs 00040006; ask team if recoverable).
  • CONTEXT.md — "Postcode search" entry; VocabularyMapping now has two producers.
  • os_places added to OverrideSourceValues (schema only — migration not yet generated).
  • Approved design mockup (build the page to this): https://claude.ai/code/artifact/d05a3ab0-48ed-41c3-94b4-b13106b7f372

Remaining work (in order)

  1. Enum migration: run ./generate_migration.sh for override_source + os_places (no .sql migrations live in-repo; script is repo-root).
  2. Routes under src/app/api/portfolio/[portfolioId]/properties/:
    • search/route.ts GET ?postcode=: normalise → postcode_search cache read (isCacheFresh; row has unique postcode + jsonb resultData) → on miss/stale call lookupPostcode (src/app/api/postcode/LookupPostcode.ts; add parliamentary_constituency to its result type) + OS pages fetch (LookupOsPlaces.ts) → upsert cache → return toAddressCandidates + geo + alreadyInPortfolio flags (one query: uprns in portfolio) + cache age.
    • route.ts POST submit {candidates, geo} (re-validate server-side): transaction over buildWritePlan rows — insert property ON CONFLICT DO NOTHING (partial unique uq_property_portfolio_uprn), upsert landlord_property_type_overrides / landlord_built_form_type_overrides ((portfolio_id, description) unique, description = OS code, source os_places), insert property_overrides snapshots (building_part=0, original_spreadsheet_description = OS code). Return {added, skipped}. Next-15 rules: await params, { error } shape.
  3. Page /portfolio/[slug]/properties/add + "Add properties" dropdown beside Export in src/app/portfolio/[slug]/components/PropertyTable.tsx ("Search by postcode" + "Bulk excel import — Soon"). Then retire toolbar AddNew dropdown. TanStack Query v4 mutations; no useEffect/useMemo (CLAUDE.md).

How to verify (patterns proven this session)

  • DB: .env.local creds; NODE_PATH node scripts with pg (ssl:{rejectUnauthorized:false}).
  • Live app: ./node_modules/.bin/next dev -p 3111; mint a session cookie with next-auth/jwt encode + NEXTAUTH_SECRET from .env.local, email @domna.homes, cookie next-auth.session-token. Server-rendered data is greppable in the RSC payload.
  • Test portfolio: 814 (new-approach). Searched-in properties will render blank/awaiting modelling — that's by design (ADR-0007), not a bug.
  • OS_API_KEY env var powers OS Places.

Gotchas

  • Properties inserted now are "new approach" (updated_at >= 2026-06-01, epcSources.ts) — every read path expects an EPC graph that won't exist until modelling. Expected.
  • Never per-scenario/per-property probe loops on plan (1.6M rows) — grouped/scoped queries only (see docs/wip/new-modelling-ui-handover.md perf notes).
  • PR #353 (Scenarios tab) is separate, open; its ADR is numbered 0003.