Commit graph

481 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
Daniel Roth
d49042eb7e add client booking reference to hubspot deal data 2026-07-06 15:46:18 +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
Daniel Roth
a6c642966e add number of attempts to hubspot deals table 2026-07-03 10:38:09 +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
Jun-te Kim
b49fbc4265 Add index on plan_recommendations.recommendation_id to Drizzle schema
Plan deletes cascade to recommendation rows, and each cascaded delete
fires an FK check against plan_recommendations.recommendation_id. With
~26M rows and no index on that column the check seq-scans per row,
making any plan deletion path time out.

The index already exists on the live DB (created CONCURRENTLY on
2026-07-02 as idx_plan_recommendations_recommendation_id, 74s build);
this brings the Drizzle schema in line so it stops drifting. No
migration is included — generating/applying it is left as a human
action, and it must reuse the existing index name (IF NOT EXISTS) so
it no-ops against prod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:24:57 +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
Daniel Roth
4956a95574 add db indexes 2026-07-02 13:38:42 +00:00
KhalimCK
ccd32fc6bb
Merge pull request #322 from Hestia-Homes/feature/update-ui-for-new-modelling
Feature/update UI for new modelling
2026-07-02 12:50:18 +01: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
Khalim Conn-Kowlessar
9bf36ad4b6 perf(backfill): faster, resumable recommendation denormalization + ops tooling
Overhaul the recommendation plan_id/material_* backfill for the 21M-row prod
table (see docs/runbooks/recommendation-denormalization-backfill.md):

- Single combined pass: set plan_id + all four material_* columns in one write
  per row (was two full passes, rewriting every row twice). Guard + COALESCE keep
  it idempotent and resumable — done rows are never rewritten.
- Session tuning: synchronous_commit=off, tunable work_mem / maintenance_work_mem.
- Range + phase controls (BACKFILL_START_ID/END_ID/SKIP_CARDINALITY/ONLY_FINALIZE)
  to run disjoint id-range workers in parallel when the disk isn't the bottleneck.
- Percent-complete + ETA in the progress log.
- Finalize builds the two new indexes plain by default (faster in a quiet
  window) with a CONCURRENTLY toggle.

The real speed-up in prod was dropping the 3 secondary indexes for the backfill
then rebuilding — the table is packed (fillfactor 100) so updates can't go HOT
and otherwise maintain every index per row. Index drop/rebuild + vacuum are kept
as standalone SQL so ops controls exactly when the app loses/regains them:

- sql/drop-recommendation-backfill-indexes.sql
- sql/rebuild-recommendation-backfill-indexes.sql (1GB maintenance_work_mem — safe
  on the 4GB instance)
- sql/vacuum-recommendation.sql

run-sql.ts runs .sql files statement-by-statement, autocommitted (so VACUUM /
CREATE INDEX CONCURRENTLY work), for environments without the psql CLI. Wired up
as npm run db:run-sql and backfill:recommendation-restore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:00:46 +00:00
Daniel Roth
dd63f46b76 migration files 2026-07-02 08:47:01 +00:00
Daniel Roth
bd6af9b74f add a new main fuel value 2026-07-02 08:46:18 +00:00
Daniel Roth
543cb69414 3 more main_heating_system values 2026-07-02 08:44:25 +00:00
Daniel Roth
4a6a0aabba 5 new main_heating_system values 2026-07-02 08:43:43 +00:00
Khalim Conn-Kowlessar
59297c3495 Merge remote-tracking branch 'origin/main' into feature/update-ui-for-new-modelling 2026-07-01 15:34:29 +00:00
Daniel Roth
34d8301f24 migration files 2026-07-01 14:44:05 +00:00
Daniel Roth
d59aa7f079 new main heating system value 2026-07-01 14:43:39 +00:00
Daniel Roth
003913a90e migration files 2026-07-01 14:22:16 +00:00
Daniel Roth
ca781771f2 add new water heating types 2026-07-01 14:21:58 +00:00
Daniel Roth
eda1079e65 migration files 2026-07-01 14:08:01 +00:00
Daniel Roth
0a67aa5e4f rename energyMainsGas to energyGasConnectionAvailable 2026-07-01 14:07:26 +00:00
Daniel Roth
7a0d2a4d64 migration files 2026-07-01 12:09:14 +00:00
Daniel Roth
57d91cae1e Add Mixed glazing to GlazingValues 2026-07-01 12:08:57 +00:00
Daniel Roth
6833bee645 migration files 2026-07-01 11:00:30 +00:00
Daniel Roth
c7ce0860c4 add flat room insulation values to RoofTypeValues 2026-07-01 10:59:53 +00:00
Daniel Roth
782ad7b1d0 migration files 2026-07-01 07:42:14 +00:00
Daniel Roth
e36fb5398d lodged_ columns on property_baseline_performance are nullable 2026-07-01 07:41:55 +00:00
Daniel Roth
813311f535 migration files 2026-07-01 07:24:19 +00:00
Daniel Roth
8789c9bab0 extend enum 2026-07-01 07:23:36 +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
fb431a1a25 wall insulation thickness is jsonb 2026-06-24 15:01:11 +00:00
Daniel Roth
4f6537bf7e migration files 2026-06-24 13:52:46 +00:00
Daniel Roth
d0b39ecdcb add new columns to table 2026-06-24 13:52:06 +00:00
Khalim Conn-Kowlessar
ea988cfe52 feat(epc): provenance signals + effective-baseline portfolio table
Theme 1 — EPC card provenance. New resolveProvenanceSignal (pure, unit-
tested) + resolveProvenance expose provenanceSignal ("estimated" /
"remodelled" / "none") and the lodged headline on property-meta. The
building-passport card shows a "Lodged EPC" badge on re-modelled
properties (even within-band), never on predicted ones; the existing
"estimated from nearby homes" banner already covers predicted.

Theme 2 — portfolio table. "Current EPC" + reporting now read the
effective baseline (effectiveSapSql/effectiveEpcBandSql); a dedicated
"Lodged EPC" column reads the source-gated lodged value (blank for
predicted); a "Predicted" pill marks estimated rows; a provenance filter
is added and the broken currentEpc/lodgedEpc filters (on null row
columns) point at the effective/lodged baselines. Expected EPC now uses
the default plan's post rating (expectedEpcRating, unit-tested) instead
of current + Σ rec SAP, which double-counted across plans (732385: A→C);
the same rec aggregate is scoped to the default plan, fixing the Cost
column's 2x double-count.

See ADR-0002. Pure logic covered by provenance.test.ts + expectedEpc.test.ts;
SQL/query/UI verified against live properties 732385 and 729529.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:21:33 +00:00
Daniel Roth
f3c03b0ba5 migration files 2026-06-22 13:03:20 +00:00