The modelling_e2e Lambda reads the active catalogue once per invocation
(SELECT * FROM material WHERE is_active ORDER BY id). Without an index this
was a full seq scan every time (~23ms), paid by all 32 concurrent Lambdas at
startup. Partial index on id WHERE is_active returns the active rows already
id-ordered — no sort, no filter.
Verified on dev: seq scan 23.2ms -> index scan 4.2ms.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A recursive persistence-fidelity guard surfaced 7 fields the SAP calculator
reads but the epc_property projection had no column for, so they were silently
lost on save->reload (same bug class as the PV arrays / floor flags). All
additive, nullable except the PV-diverter bool; no backfill (defaults match
the domain defaults).
- epc_main_heating_detail: community_heating_boiler_fuel_type,
community_heating_chp_fraction (community-heated CHP).
- epc_building_part: alt_wall_1_is_sheltered, alt_wall_2_is_sheltered, and
wall_insulation_thermal_conductivity (jsonb to preserve Union[int,str]).
- epc_property: energy_pv_diverter_present (NOT NULL DEFAULT false),
heating_cylinder_volume_measured_l, ventilation_air_permeability_ap50_m3_h_m2.
Includes generated migration 0246_minor_lady_bullseye.sql + snapshot/journal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add EPC persistence schema for two round-trip-fidelity gaps so the Model
backend can store the full EpcPropertyData faithfully:
- New child table epc_photovoltaic_array (one row per PV array), mirroring
the epc_window child-table pattern.
- epc_floor_dimension: add is_exposed_floor and is_above_partially_heated_space
boolean flags (NOT NULL DEFAULT false; additive, no backfill).
Includes generated migration 0245_magenta_nomad.sql + snapshot/journal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Display the UPRN beneath the selected address on the remote assessment
page so it's available for quicker debugging. The value is already on
the selected address object from the postcode lookup; it just wasn't
rendered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a NOT NULL DEFAULT false `marked_for_deletion` boolean to the
`property` table. Hard deletes are expensive (cascading child rows,
indexes), so a row is flagged here and a separate cron job performs the
physical delete later; reads can filter on the flag in the interim.
A constant default makes this a metadata-only ALTER on Postgres 11+ (no
table rewrite); existing rows backfill to false.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a `source` discriminator to `epc_property` so a property can hold a
lodged and a predicted EPC at the same time, per the EPC Prediction
production-wiring branch in the Model repo (docs/MIGRATION_NOTE_predicted_epc_source.md).
- `source` text NOT NULL default 'lodged' — backfills every existing row as
a real lodged EPC.
- Unique index becomes (property_id, portfolio_id, source) so lodged +
predicted rows can coexist for the same property/portfolio.
- New (property_id, source) index — lodged/predicted reads filter on both.
Allowed values: 'lodged' | 'predicted'.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The async finaliser (ADR-0005) introduced the `finalising` status, but the
server page's STATUS_CONFIG had no entry for it, so it fell through to the
`ready_for_processing` fallback ("Awaiting column mapping") and never mounted the
live poller — the page looked stuck even though the Lambda had inserted the
properties and written `complete`.
- Add the `finalising` card ("Uploading to ARA") to STATUS_CONFIG.
- Render OnboardingProgress during `finalising` so it polls live.
- Refresh the server page once when the poll first sees a terminal status
(guarded by a new `serverStatus` prop to avoid a loop; uses react-query v4
onSuccess, no useEffect) so it advances to the "Processing complete" card.
- Add a `finalising` → "Uploading to ARA" badge on the uploads list.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For uploads with classifier columns, surface the classifier's
description→enum assumptions on the awaiting_review screen so the user can
correct any (written source='user') and acknowledge before Finalise.
Previously only the multi-entry order step existed, so non-multi-entry
uploads got no classification review at all and the assumptions were
applied silently.
- detectMultiEntry: capture a sample whenever classifier columns are
mapped (largest-count row if multi-entry, else first classified row);
the sample now carries all classifier columns. "sample != null" means
"there is something to verify"; largestCount >= 2 stays the multi-entry
signal.
- setVerifyAck + verify-classification PATCH route + useConfirmVerification.
- VerifyClassificationPanel (Step 1); MultiEntryOrderingPanel slimmed to
order-only with read-only classification annotation; canFinalize gated
on both steps where each applies.
- Unit tests for detectMultiEntry + ordering helpers.
The verify_ack column + 0219 migration landed separately via #303.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#298: read the classifier's resolved enums for the multi-entry sample's
entries (joined from landlord_*_overrides by normalized description) and show
each beside the raw text, or "not classified" when absent.
#299: make each classification editable — a dropdown of the category's valid
enum values whose selection upserts the override by (portfolio, description)
with source='user', so the classifier never re-clobbers it. UI notes the
portfolio-wide blast radius. Verification ack is folded into the existing
order-confirm (no separate flag/migration); editing is optional review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the multiEntryOrdering jsonb column + interactive order picker: the
largest-count multi-entry sample is shown with a building-part dropdown per
file position (one Main building + Extensions), validated as a permutation.
A PATCH route persists { count: permutation } + confirmed, and Finalise is
disabled until the ordering is confirmed when the upload is multi-entry.
Migration for the new column is intentionally not included here — generated
after origin/main is merged (its multi_entry_summary migration lands first).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 0222-0225 batch backfilled plan_id/material_* onto recommendation
inside drizzle's single migration transaction. On the production table
(~26M rows) that held an AccessExclusiveLock for hours, blocked unrelated
migrations, exhausted EBS throughput credits, and could not report
progress or resume.
Split schema DDL from data backfill:
- 0222/0224: keep only instant metadata-only DDL (ADD COLUMN + FK
NOT VALID); drop the inline CREATE INDEX.
- 0223/0225: backfills removed (now no-ops pointing to the script).
- New src/app/db/backfill-recommendation-denormalization.ts: idempotent,
resumable, batched (committed per batch) backfill that then builds the
indexes CONCURRENTLY and validates the FKs online.
- CONTEXT.md: document recommendation->plan (1:1) and recommendation->
material (now at most 1; legacy multi-material rows reconciled).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>