Commit graph

287 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
4fd1e00e63 perf(db): covering partial index for the reporting measures aggregate
The reporting 'where the money goes' panel joins the latest plan per
property to its recommendations ON plan_id, filters to the default active
set, and sums estimated_cost / counts property_id per measure_type. The
only plan_id index (idx_recommendation_plan_id) is plain, so the aggregate
heap-fetches every recommendation to apply the filter and read those
columns — hundreds of thousands of fetches on large portfolios (434),
which times the panel out.

idx_recommendation_plan_default carries plan_id + measure_type +
property_id + estimated_cost as key columns (drizzle-orm has no INCLUDE),
partial on default = true AND already_installed = false, so the aggregate
runs index-only. Added to the schema and generated via drizzle-kit.

OPS: build CONCURRENTLY out-of-band on prod before the migration runs so it
no-ops there (0258 pattern); the migration uses IF NOT EXISTS for fresh
environments. See the migration file header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:52:35 +00:00
Jun-te Kim
3a1f9146b7 Merge branch 'main' of https://github.com/Hestia-Homes/assessment-model into feature/uprn-confirmation-page
# Conflicts:
#	src/app/db/migrations/meta/0261_snapshot.json
#	src/app/db/migrations/meta/_journal.json
2026-07-07 17:53:42 +00:00
Jun-te Kim
e0d2b9dbef feat(bulk-upload): add uprn corrections table for pre-finalise confirmation (ADR-0057)
Foundation for the "Addresses" review tab: captures the user's confirmed
UPRN per combiner row, keyed by source_row_id (no property.id exists
pre-finalise). At dispatchFinaliser these are overlaid onto the combiner
CSV so the finaliser builds identity + property_overrides from the
confirmed UPRN in one pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 15:46:58 +00:00
Khalim Conn-Kowlessar
4073671127 feat(db): add user_portfolio_config and user_portfolio_folders tables
Per-user portfolio workspace layer for the home-page redesign: starred
portfolios (starred_at, non-null = starred, orderable) and personal
folders (name + position, drag-reorderable). Composite FK
(folder_id, user_id) -> (id, user_id) makes cross-user folder
assignment impossible at the database level. Config rows are created
lazily via upsert on (user_id, portfolio_id); portfolioUsers remains
pure access control.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:23:15 +00:00
Jun-te Kim
114ccbba02 Add certificate_number column to property table
The address2uprn lookup already returns the EPC certificate number
alongside the UPRN, but only the UPRN is persisted today. This adds
a nullable column so a follow-up change can start saving it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-07 11:50:59 +00:00
Khalim Conn-Kowlessar
fe521aa8d5 feat(db): add inputs column to tasks
Mirrors sub_task.inputs (JSON as text): the task's request inputs. For a
modelling run (ADR-0008, feature/bulk-trigger-modelling) the task row itself
carries the run's config — portfolio id, scenario ids, filters — with
sub_tasks holding the per-property execution work. Column-only change so it
can be applied to the database ahead of the feature merging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:31:11 +00:00
Daniel Roth
5fb1f92d08 migration files 2026-07-06 15:46:41 +00:00
Khalim Conn-Kowlessar
c8c00b87b5 feat(db): add os_places to override_source enum
New provenance value for the deterministic OS Places classification-code
mapping written by the postcode-search journey (ADR-0007, on
feature/add-properties-by-postcode). Enum-only change so it can be
applied to the database ahead of the feature merging.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:46:33 +00:00
Daniel Roth
cd8a43c798 migration files 2026-07-03 10:38:25 +00:00
Jun-te Kim
8e39c00ec8 Add journal-tracked migration 0258 for the recommendation_id index
Generated with drizzle-kit generate, then hand-edited to IF NOT EXISTS:
the index already exists on the live DB (created CONCURRENTLY on
2026-07-02), so the migration must no-op there while still building the
index in fresh environments. Snapshot + journal are updated so the next
generated migration doesn't re-emit this index.

Applying it (drizzle-kit migrate) remains a human action.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:30:42 +00:00
Daniel Roth
8ebb0d7fec amend migration files 2026-07-02 13:48:46 +00:00
Daniel Roth
03ba8400ff Build the new indexes CONCURRENTLY (matches 0141)
The DB is under load; a plain CREATE INDEX blocks writes for the
duration of each build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 13:41:12 +00:00
Daniel Roth
7eb2705817 migration files 2026-07-02 13:39:05 +00:00
Jun-te Kim
4ec648fb1a Add 'Secondary glazing' to the glazing pgEnum
Hyde 796 landlord descriptions include '100% secondary glazing (sap 9.94)'
(39 properties), currently flattened to 'Single glazing' (U = 4.8 instead of
secondary's 2.9). The calculator already supports secondary glazing (SAP10
code 5) — only this FE-owned enum and the model repo's GlazingType lacked a
member. Additive ALTER TYPE ... ADD VALUE only, generated via drizzle-kit.

Pairs with the model-repo GlazingType.SECONDARY change (Model#1416).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 10:55:36 +00:00
Daniel Roth
dd63f46b76 migration files 2026-07-02 08:47:01 +00:00
Daniel Roth
34d8301f24 migration files 2026-07-01 14:44:05 +00:00
Daniel Roth
003913a90e migration files 2026-07-01 14:22:16 +00:00
Daniel Roth
eda1079e65 migration files 2026-07-01 14:08:01 +00:00
Daniel Roth
7a0d2a4d64 migration files 2026-07-01 12:09:14 +00:00
Daniel Roth
6833bee645 migration files 2026-07-01 11:00:30 +00:00
Daniel Roth
782ad7b1d0 migration files 2026-07-01 07:42:14 +00:00
Daniel Roth
813311f535 migration files 2026-07-01 07:24:19 +00:00
Jun-te Kim
f51fdca3d8 Add partial index on material(is_active) for the catalogue read
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>
2026-06-29 11:53:19 +00:00
Khalim Conn-Kowlessar
e4960db2c1 feat(epc): persist 7 calculator-read fields dropped on save
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>
2026-06-26 18:34:45 +00:00
Khalim Conn-Kowlessar
a1548ca3a9 feat(epc): persist PV arrays and floor heat-loss flags
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>
2026-06-26 16:38:36 +00:00
Jun-te Kim
752ffe1935 feat(db): add property.marked_for_deletion soft-delete flag
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>
2026-06-25 12:18:32 +00:00
Daniel Roth
1b7d314fdd SQL correction 2026-06-24 15:06:43 +00:00
Daniel Roth
e3a58e7e9c migration files 2026-06-24 15:02:21 +00:00
Daniel Roth
4f6537bf7e migration files 2026-06-24 13:52:46 +00:00
Daniel Roth
f3c03b0ba5 migration files 2026-06-22 13:03:20 +00:00
Jun-te Kim
1ac4e20c4c db changes 2026-06-19 15:04:43 +00:00
Jun-te Kim
ae74133575 feat(db): add epc_property.source for predicted-EPC slot (ADR-0031)
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>
2026-06-16 13:46:19 +00:00
Daniel Roth
10f7d4c188 migration 2026-06-15 08:59:46 +00:00
Daniel Roth
954fb5b16e migration 2026-06-15 08:52:41 +00:00
Daniel Roth
3425dae3cc migration files 2026-06-10 14:25:37 +00:00
Daniel Roth
2e9ec32c48 migration files 2026-06-09 13:57:31 +00:00
Jun-te Kim
c2b7e00075 new table for new deal proeprties we want 2026-06-08 14:42:50 +00:00
Daniel Roth
7e2be3efbd Merge branch 'main' into feature/magicplan-ventilation-tables 2026-06-08 12:21:22 +00:00
Daniel Roth
b16e0f7d3d new tables for magicplan ventilation. move opening type from window table to window ventilation table 2026-06-08 09:25:16 +00:00
Khalim Conn-Kowlessar
6f38677da3 Make recommendation denormalisation backfill safe and online
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>
2026-06-08 08:36:39 +00:00
Daniel Roth
d2c9c0696a add guard clause when backfilling plan_id to recommendation 2026-06-05 10:13:49 +00:00
Daniel Roth
694bac47e1 backfill materials columns for recommendation table 2026-06-05 10:13:31 +00:00
Daniel Roth
62b10ef808 migration for materials columns on recommendation table 2026-06-05 10:03:34 +00:00
Daniel Roth
a2a5d7212a backfill plan_id using plan_recommendations 2026-06-05 08:16:27 +00:00
Daniel Roth
c40e64bdec add plan_id column to recommendation table 2026-06-05 08:12:57 +00:00
Jun-te Kim
fa9bf538da Add property_overrides table + override_component enum (0221)
The per-Property fact layer deferred by ADR-0004: one row per
(property, building_part, override_component) holding the resolved
landlord-override enum as a denormalised text snapshot, plus the raw
spreadsheet description it resolved from.

Schema only — no writer yet. The bulk_upload_finaliser application will
populate it (recalculate-on-rerun via upsert on the unique key). Design
and rationale (snapshot-not-FK, drop source, recalculate semantics) in
docs/design/bulk-upload-finaliser.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 09:52:37 +00:00
Daniel Roth
2011847aa7 new migration files 2026-06-03 12:55:49 +00:00
Daniel Roth
fb952a6716 update migration sql so that new jsonb fields are mapped correctly 2026-06-03 10:49:54 +00:00
Daniel Roth
9c918c29df migration files 2026-06-03 10:43:32 +00:00
Jun-te Kim
ca4946f594 Add verify_ack column to bulk_address_uploads (0219)
The "Verify classification" acknowledgement flag for ADR-0004 Step 1.
Gates Finalise whenever an upload has classifier columns, independent of
multi-entry, so it lives in its own column rather than on
multiEntryOrdering.

Plain additive column (boolean NOT NULL DEFAULT false), no data backfill,
so it applies cleanly with either `drizzle-kit migrate` or `push`. The
feature that reads/writes it lands separately on
feature/frontend_landlord_overrides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 17:54:01 +00:00