Commit graph

1565 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
2f0f937fb6 fix(reporting): read scenario measures via denormalised recommendation.plan_id
The scenario measures modal came up empty because both measures routes
gated on EXISTS against plan_recommendations — a retired join table with
no rows for new-approach plans (25.7M legacy rows, none for e.g.
portfolio 812's plans), so the query returned zero measures.

Read the denormalised model instead: drive from the latest plan per
property (default or scenario) and JOIN recommendation by the indexed
property_id, scoped to the plan via recommendation.plan_id. Portfolio 812
default now returns 5 measures (solar_pv 54 homes/£266k, …) where it
returned 0. Also removes the stale commented query block that referenced
the retired plan_recommendations / recommendation_materials tables.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:42:19 +00:00
Khalim Conn-Kowlessar
eef9fdc171 fix(reporting): show scenario bars for EPC bands absent from the baseline
The breakdown chart looped only over baseline bands (epcBands), which the
query returns only for bands that have properties. So a band reached ONLY
after the scenario — e.g. A/B when no property starts that high — had no
row, and its scenario bar never rendered (portfolio 812: scenario yields
7 A + 2 B, baseline has none). Iterate a canonical A–G+Unknown order over
baseline ∪ scenario instead, so post-scenario-only bands still appear.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:25:45 +00:00
Khalim Conn-Kowlessar
7de48448c0 fix(reporting): temp guards for sub-baseline scenario post_sap
The modelling writes a target-level post_sap (e.g. ~C) even for homes
already above the scenario target, so plans can carry cost while modelling
post_sap BELOW the effective baseline. That skewed three reporting
surfaces. Three TEMP (demo) guards, all keyed on the effective baseline
(ADR-0002); revert once the Model team fixes the sub-baseline plans:

1. EPC band chart: post-scenario SAP clamped to GREATEST(baseline, post)
   so already-compliant properties aren't shown "improving" down a band
   (portfolio 796: EPC B 4,244 -> 1,660 wrongly, now 4,479).
2. n_units_upgraded + cost: exclude plans whose post_sap is below the
   effective baseline (not real upgrades) -- 796: 10,283 -> 9,765, -£1.28M.
3. total_sap_uplift / £-per-SAP: baseline is the effective SAP, not the
   null-for-new-approach current_sap_points, and counts genuine gains
   only -- uplift 0 -> 89,724, so £/SAP £0 -> £536.

Also fixes the no-plan branch to use the effective baseline instead of
the null current_sap_points (Unknown-band leakage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:44:22 +00:00
Khalim Conn-Kowlessar
7bb2b093e5 perf(portfolio): only join EPC graph / plan LATERAL in count when a filter needs it
getPropertiesCount returns the portfolio's total property count for
pagination, but it dragged the whole read model through the COUNT: the
property_details_epc + property_baseline_performance + two epc_property
joins, plus a correlated default-plan LATERAL that ran once per property
(31k+ plan lookups for a large portfolio). None of those joins change a
COUNT (none multiply rows), so for an unfiltered load they were pure cost
— pushing the query to ~14.7s, past Vercel's 15s limit (intermittent
timeout on /api/properties).

Join only what an active filter references: no filters -> plain COUNT over
property; EPC/provenance filter -> add the epc-graph joins; Expected-EPC
filter -> add the plan LATERAL. Unfiltered count 14,667ms -> 93ms
(portfolio 796); provenance-filtered 156ms. getProperties is unchanged
(its LIMIT 1000 already bounds the LATERALs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:51:54 +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
Khalim Conn-Kowlessar
ec88dadae2 docs: record effective-as-canonical-current decision + provenance terms
ADR-0002: Effective performance is the canonical "current" for display and
reporting; lodged shown alongside (supersedes the mid-migration "reporting
stays lodged" note). CONTEXT.md gains EPC-provenance and provenance-signal
terms, and flags that the UI "Current EPC" surfaces show Effective.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 22:15:08 +00:00
Khalim Conn-Kowlessar
e51bf1d23f fix(building-passport): use effective (modelling) baseline for current SAP/EPC/CO2
Widen the lodged->effective switch from plan-only to all per-property
building-passport views (overview, assessment, plans). New-approach
properties' "current" SAP/EPC/CO2/primary-energy now come from the
effective (re-baselined) figure the modelling and plans were scored
against, so a property's current state agrees with the plan maths --
re-baselining (e.g. pre_sap10) otherwise made plans read as flat or as a
downgrade against the lodged gov-register EPC.

Done by flipping the shared object resolvers (resolvePropertyHeadline,
resolveDetailsEpcMeta, resolveNewConditionReport) to effective_*, which
removes the plan-only getPlanPropertyMeta/resolveEffectiveHeadline
indirection added in the previous commit. The set-based reporting
fragments (sapSql/epcBandSql/carbonSql/...) stay on lodged_* so portfolio
and reporting totals remain matched to the gov register.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:47:06 +00:00
Khalim Conn-Kowlessar
65db15d3c7 fix(plans): show effective (modelling) baseline as plan "current" SAP/EPC
Plans are scored against the effective baseline on
property_baseline_performance, which re-baselining can move away from the
lodged EPC shown everywhere else. Using lodged as a plan's "current"
misrepresented the uplift — e.g. lodged C/71 vs effective D/64 with a
post-retrofit C/69.6 read as a downgrade (71 → 69.6) instead of the real
D → C improvement.

Add resolveEffectiveHeadline + getPlanPropertyMeta and switch both plan
pages to it. Legacy properties and backfill gaps fall back to the
lodged/row values.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:15:37 +00:00
Khalim Conn-Kowlessar
a89e6bfe30 feat(building-passport): show longitude/latitude in Location & Status
Local authority and constituency have no data source for new-approach
properties (not on epc_property; null on the property row). Replace them with
longitude/latitude from property_details_spatial (already fetched on the page),
keeping Tenure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:44:57 +00:00
Khalim Conn-Kowlessar
1aeb33762e fix(building-passport): fill tenure from EPC graph
Tenure read the property-row tenure column, null for new-approach properties.
Backfill propertyMeta.tenure from epc_property.tenure (mostly text labels like
"Rented Social"; numeric SAP codes 1/2/3 mapped to the same labels).

Local authority / constituency have no source in the new pipeline (not on
epc_property, null on the property row, energy_assessments doesn't cover these
properties) — left as-is pending the pipeline writing them to the property row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:30:46 +00:00
Khalim Conn-Kowlessar
9750770c7c fix(building-passport): fill storeys and habitable rooms from EPC graph
On the overview page, Storeys read epc_property.number_of_storeys (null for some
properties) and Habitable rooms read the property-row number_of_rooms (null for
new-approach properties).

- Habitable rooms: backfill propertyMeta.numberOfRooms from
  epc_property.habitable_rooms_count (always populated).
- Storeys: fall back to the main building part's floor-dimension count when
  epc_property.number_of_storeys is null.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 11:24:25 +00:00
Khalim Conn-Kowlessar
a1ba5b1e0d fix(assessment): handle text + numeric property_type/built_form; correct age bands
epc_property stores property_type/built_form either as numeric RdSAP codes
(newer rows) or plain text ("House", "Detached" — most rows). The previous code
only mapped codes and fell through to dwelling_type for text rows, showing
"Semi-Detached house" instead of "House". Now: map numeric codes, pass text
through as-is, fall back to dwelling_type only when absent.

Also align AGE_BAND_YEARS with the backend ConstructionAgeBand enum: L is
2012–2022 (not "2012 onwards") and add M = 2023 onwards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:23:00 +00:00
Khalim Conn-Kowlessar
b7cbd5c03f fix(assessment): fill property type / built form / age / floor height from EPC graph
For new-approach properties the property-row text columns (property_type,
built_form, year_built) are null, and the condition report had no floor height,
so the assessment page showed Unknown for several general features.

- property-meta: backfill property type / built form / age from epc_property
  (+ epc_building_part construction age band), mapping RdSAP codes to labels
  (property_type 2 -> Flat, built_form 3 -> End-Terrace, age band F -> 1976–1982),
  falling back to the raw value for unmapped codes.
- condition report: source floor height from the main building part's
  epc_floor_dimension.room_height_m.
- formatGeneralFeatures: use ?? instead of || so a real 0 (open fireplaces /
  extensions) shows as 0 rather than "Unknown".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 10:02:53 +00:00
Khalim Conn-Kowlessar
5dd2179bef fix(properties): source current SAP/EPC from baseline in the portfolio table
getProperties selected p.current_sap_points / p.current_epc_rating directly,
which are null for new-approach properties (headline columns unwritten on the
row). That made the Current EPC column blank and, since the Expected EPC column
is currentSapPoints + recommendation SAP, made Expected EPC show 0.

Use the sapSql / epcBandSql baseline helpers (lodged_sap_score / lodged_epc_band
for new-approach properties, property row for legacy), matching the reporting
page. Verified for properties that have a baseline row; the rest populate once
the baseline backfill completes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:37:30 +00:00
Khalim Conn-Kowlessar
04d9b45f0a perf(properties): stop reading property_targets and funding_package
Both were per-property LATERAL lookups against tables with no index on the
join column (property_targets.property_id, funding_package.plan_id), so each
seq-scanned 150k-180k rows once per property — ~3.4s of the load for a
291-property portfolio came from property_targets alone.

Neither value is actually used by the portfolio table: the "Expected EPC" column
derives from currentSapPoints + recommendation SAP, and fundingScheme is fetched
separately by the proposals table. Drop both reads (kept as NULL to preserve the
PropertyWithRelations shape) and trim the plan LATERAL to the columns used.
291-property portfolio: ~3.5s -> ~85ms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:21:16 +00:00
Khalim Conn-Kowlessar
fec714a406 fix(property-meta): resolve in-process, drop self-fetch to process.env.URL
getPropertyMeta self-fetched process.env.URL + /api/property-meta. That URL can
resolve to a different deployment (e.g. production), so the building-passport
pages were reading property meta from OLD code — the backfilled headline SAP/EPC
never arrived and the current rating rendered blank, regardless of caching.

Extract resolvePropertyMeta() and call it directly from both the route and
getPropertyMeta, so the page runs identical in-process logic. JSON round-trip
through serializeBigInt preserves the previous shape (bigints/dates as strings).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:40:17 +00:00
Khalim Conn-Kowlessar
e1d56cc773 fix(building-passport): don't cache property-meta fetch
getPropertyMeta self-fetched /api/property-meta with revalidate:60 (despite a
comment saying it shouldn't cache). Vercel's Data Cache served a stale response
from before the headline backfill, so the current EPC rating / SAP rendered
blank on the plans and plan-detail pages even though the API now returns them.
Use cache:"no-store" so backfilled headline values propagate immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:31:38 +00:00
Khalim Conn-Kowlessar
a184127cd4 fix(plan-detail): read recommendations denormalised, not via plan_recommendations
getRecommendations read the retired plan_recommendations join table, which is
empty for new-approach plans, so the plan detail page showed no recommendations.
Read the recommendation table directly. Because recommendation.plan_id is
unindexed (26M rows), key off the plan's property_id (indexed) and filter
plan_id in memory — fast for any plan, default or not.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:25:41 +00:00
Khalim Conn-Kowlessar
5d87481b6b fix(plans): source headline SAP/EPC from baseline for new properties
The plans page computed the plan uplift as postSapPoints - currentSapPoints,
but current_sap_points is unwritten on the property row for new-approach
properties (null -> 0), so the uplift showed the full post score (e.g. +71.39)
and no current rating.

property-meta now backfills currentSapPoints / currentEpcRating from
property_baseline_performance (lodged_sap_score / lodged_epc_band) for
new-approach properties, fixing every propertyMeta consumer (plans page, hero
cards, building passport). e.g. 709634: current 60 (D), uplift now +11.39.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:20:49 +00:00
Khalim Conn-Kowlessar
14f06abb6d perf(properties): LATERAL lookups + recommendation by property_id
/api/properties timed out (15s) on just 12 properties: joining the 26M-row
recommendation table on the unindexed plan_id forced a full-table hash, and the
multi-million-row plan/funding joins compounded it.

Rewrite getProperties/getPropertiesCount to read the denormalised model (no
plan_recommendations / recommendation_materials): one-row LATERAL lookups for
target/plan/funding and a recommendation aggregate keyed by the INDEXED
recommendation.property_id, dropping the GROUP BY entirely. ~1s -> instant for a
12-property portfolio.

Also fix the reporting page for new-approach properties (current_sap_points /
current_epc_rating on the property row are unwritten): source avg SAP and the
EPC band distribution from property_baseline_performance (lodged_sap_score /
lodged_epc_band) via new sapSql / epcBandSql helpers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 14:07:09 +00:00
Khalim Conn-Kowlessar
f3e00429f9 fix(epc): source baseline carbon/energy/bills from lodged columns
Per domain semantics: lodged_* on property_baseline_performance is the
gov-registry (actual) figure, effective_* is the modelling-adjusted baseline.
Use the lodged columns for the "current" carbon/primary-energy display and the
individual cost columns (summed) for bills. Bills return NULL when no baseline
row exists, so they're excluded from AVG/SUM rather than shown as £0.

(property_baseline_performance is still unwritten DB-wide; this makes the reads
correct for when the pipeline populates it.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 01:07:00 +00:00
Khalim Conn-Kowlessar
ef55c49e43 fix(epc): tolerate absent EPC data + read recommendations via plan_id
For new-approach properties the modelling pipeline currently writes only
epc_property + epc_energy_element + plan/recommendation — property_baseline_
performance, epc_property_energy_performance metrics and the property-row
headline columns are not yet populated.

- layout: guard propertyMeta.detailsEpc?.estimated (detailsEpc can be absent)
- resolveConditionReport: degrade to an all-Unknown report instead of throwing
  when the EPC graph/legacy row is missing (no more 500s)
- getProperties: join recommendation via recommendation.plan_id instead of the
  retired plan_recommendations table, so Plan Cost / recommendation SAP populate

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 01:00:31 +00:00
Khalim Conn-Kowlessar
6026944fe5 feat(epc): read new EPC graph for new-approach properties via resolver
The new modelling pipeline no longer writes property_details_epc. Route all
EPC reads through src/lib/services/epcSources.ts, which branches on the cutoff
(property.updated_at >= 2026-06-01): legacy rows keep reading
property_details_epc; new-approach rows read epc_property (+ children),
property_baseline_performance, and plan/recommendation.

- per-property: resolveConditionReport (building passport),
  resolveDetailsEpcMeta (/api/property-meta)
- aggregates: shared carbon/bills/energy/estimated/expired/floor-area/
  lodgement/mainfuel SQL fragments + newApproachJoins, wired into the
  portfolio list + filters, reporting metrics, and both scenario metrics routes

estimated = predicted-only EPC. Gracefully omits gap fields for new
properties (mainfuel, energyTariff, floorHeight, installed-measures bill
adjustment, SAP-05 likely-downgrade).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 00:40:37 +00:00
Jun-te Kim
e606006e6b
Merge pull request #321 from Hestia-Homes/feature/epc-prediction-source-column
feat(db): add epc_property.source for predicted-EPC slot (ADR-0031)
2026-06-16 14:51:07 +01: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
7feec70c98
Merge pull request #320 from Hestia-Homes/feature/document-viewing-settings
Fix expected file types unit tests
2026-06-15 14:43:31 +01:00
Daniel Roth
e3ee1480aa dont hardcode expected file count in tests 2026-06-15 13:42:03 +00:00
Daniel Roth
8e9e093def fix brokn unit tests 2026-06-15 13:38:01 +00:00
Daniel Roth
273932eb30
Merge pull request #318 from Hestia-Homes/feature/document-viewing-settings
Don't include PAR photopack in MIssing Documents list
2026-06-15 14:32:08 +01:00
Daniel Roth
86642de2a2 Don't include PAR photopack in MIssing Documents list 2026-06-15 13:06:28 +00:00
Daniel Roth
f0bb2b237a
Merge pull request #316 from Hestia-Homes/feature/new-cols-in-property-export
Additional columns in property export
2026-06-15 13:08:15 +01:00
Daniel Roth
2a9da6765b Switch EPC SAP (Potential) data source from epcSapScorePotential to potentialPostSapScoreDropdown 2026-06-15 11:57:39 +00:00
Daniel Roth
5ad99df044 correct typo 2026-06-15 09:54:15 +00:00
Daniel Roth
32bb2e39ef Merge branch 'main' into feature/new-cols-in-property-export 2026-06-15 09:50:16 +00:00
Daniel Roth
c72b7c1625 Add coordination comments, damp/mould, and Domna survey columns to property table and CSV export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-15 09:48:41 +00:00
Daniel Roth
2b380d83d8
Merge pull request #315 from Hestia-Homes/remove-duplicate-hs-col
Remove "osmosis_survey_required" column as it is already in the db as "domna_survey_required"
2026-06-15 10:21:10 +01:00
Daniel Roth
10f7d4c188 migration 2026-06-15 08:59:46 +00:00
Daniel Roth
db0b3670f4 delete col 2026-06-15 08:59:24 +00:00
Daniel Roth
462cd36c3f
Merge pull request #314 from Hestia-Homes/feature/new-hs-deal-cols
Add "osmosis_survey_required" to hs deal table
2026-06-15 09:57:10 +01:00
Daniel Roth
954fb5b16e migration 2026-06-15 08:52:41 +00:00
Daniel Roth
a3ee12a418 osmosis_survey_required column 2026-06-15 08:50:41 +00:00
Daniel Roth
4ea1fa48d4
Merge pull request #313 from Hestia-Homes/feature/hs-last_submission_date
last_submission_date column on hubspot deals table
2026-06-11 10:59:06 +01:00
Daniel Roth
3425dae3cc migration files 2026-06-10 14:25:37 +00:00
Daniel Roth
a5e07ffa1c add last_submission_date to hs deal table 2026-06-10 14:23:55 +00:00
Jun-te Kim
23a275ae85
Merge pull request #312 from Hestia-Homes/feature/new-file-type-and-source-for-audits
New file type and source for audits
2026-06-09 15:21:39 +01:00
Daniel Roth
2e9ec32c48 migration files 2026-06-09 13:57:31 +00:00
Daniel Roth
de963bbc39 add file type and file source 2026-06-09 13:56:52 +00:00
Jun-te Kim
d0d5ec0366
Merge pull request #310 from Hestia-Homes/feature/frontend_landlord_overrides
Feature/frontend landlord overrides
2026-06-08 16:23:21 +01:00
Jun-te Kim
c6091af34c
Merge pull request #311 from Hestia-Homes/feature/additional_hubspot_proeprties
Feature/additional hubspot proeprties
2026-06-08 16:12:18 +01:00
Jun-te Kim
c2b7e00075 new table for new deal proeprties we want 2026-06-08 14:42:50 +00:00