docs: handover for postcode-search build continuation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-06 13:38:54 +00:00
parent 5b224677dc
commit 302e200ed6

View file

@ -0,0 +1,58 @@
# 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.