From 1cd48bc78e6dcca95f23f99eb318a0041d1e9873 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Thu, 2 Jul 2026 14:52:21 +0000 Subject: [PATCH] =?UTF-8?q?fix(reporting):=20negative=20scenario=20carbon?= =?UTF-8?q?=20impact=20=E2=80=94=20move=20carbon=20fragments=20to=20effect?= =?UTF-8?q?ive=20baseline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Total carbon saved" on the reporting scenario overlay went negative (portfolio 796: -10,673 t/yr, £/tCO2 -8,195). Cause: the baseline side (getTotals → carbonSql) summed bp.lodged_co2_emissions_t_per_yr, which is NULL for predicted-only properties (12,064 of 796's 31,919 — no mirrored lodged estimate is written), so they contributed nothing to the baseline. The scenario side counted their plans' post_co2_emissions (21,046 t) in full, dragging saved below zero. The same subtraction fed £-per-tonne-CO2 and average-per-unit. carbonSql and energyConsumptionSql were left on lodged_* when ADR-0002 (effective performance is the canonical current, superseding "reporting stays on lodged") migrated the SAP fragments — the same reporting cards were already mixing effective SAP with lodged carbon. Move both to effective_*, which is populated for every property with a baseline row and is the figure the plans' post_* values were scored against. All call sites (reporting averages/totals, both scenario metrics routes, portfolio table CO2 column/filter) are current-state surfaces, so none wanted lodged. Legacy branch untouched. Verified live on 796: baseline and scenario totals computed in one snapshot with the shared fragment give saved = +4,059 t/yr (a plan regeneration was writing 796 mid-diagnosis, so values drift run-to-run; pre-fix the same snapshot maths gives ~ -13k and worsening as plans land). Legacy portfolio 433 totals unchanged. No regression-test seam exists for cross-query consistency (no DB test harness); consistency is structural — both sides compose the same carbonSql fragment. Co-Authored-By: Claude Fable 5 --- src/lib/services/epcSources.ts | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/lib/services/epcSources.ts b/src/lib/services/epcSources.ts index b50cdd78..8dd28206 100644 --- a/src/lib/services/epcSources.ts +++ b/src/lib/services/epcSources.ts @@ -77,16 +77,19 @@ export const newApproachJoins = sql` `; /** - * Baseline carbon (t CO₂/yr). New: the lodged (gov-registry) value. Legacy: - * e.co2_emissions. Swap to `effective_co2_emissions_t_per_yr` if the - * modelling-adjusted baseline is wanted instead. + * Baseline carbon (t CO₂/yr). New: the effective (re-baselined) value — the + * canonical "current" per ADR-0002, and the baseline the plans' post_* figures + * were scored against. The lodged value is NULL for predicted-only properties + * (no mirrored estimate is written), which made lodged-based reporting totals + * silently exclude them — and drove "carbon saved" negative whenever their + * plans' post carbon counted on the scenario side. Legacy: e.co2_emissions. */ export const carbonSql = (e: Alias) => - sql`CASE WHEN ${isNewApproachSql} THEN bp.lodged_co2_emissions_t_per_yr ELSE ${e}.co2_emissions END`; + sql`CASE WHEN ${isNewApproachSql} THEN bp.effective_co2_emissions_t_per_yr ELSE ${e}.co2_emissions END`; -/** Primary energy intensity (kWh/m²/yr). New: lodged value. Legacy: e.primary_energy_consumption. */ +/** Primary energy intensity (kWh/m²/yr). New: effective value (ADR-0002, as carbonSql). Legacy: e.primary_energy_consumption. */ export const energyConsumptionSql = (e: Alias) => - sql`CASE WHEN ${isNewApproachSql} THEN bp.lodged_primary_energy_intensity_kwh_per_m2_yr ELSE ${e}.primary_energy_consumption END`; + sql`CASE WHEN ${isNewApproachSql} THEN bp.effective_primary_energy_intensity_kwh_per_m2_yr ELSE ${e}.primary_energy_consumption END`; /** * Baseline SAP score. New: the lodged (gov-registry) score from @@ -566,9 +569,9 @@ export async function resolveConditionReport( * effective score away from the lodged (gov-registry) EPC, so showing lodged as * "current" misrepresents a plan's uplift — e.g. lodged C/78 vs an effective * baseline of C/71 with a post-retrofit C/71.4 reads as flat instead of the real - * improvement. The set-based reporting fragments (`sapSql`, `epcBandSql`, - * `carbonSql`, …) deliberately stay on `lodged_*` to keep portfolio/reporting - * totals matched to the gov register. + * improvement. The set-based reporting fragments (`effectiveSapSql`, + * `carbonSql`, `energyConsumptionSql`, …) read `effective_*` too (ADR-0002); + * only the dedicated "Lodged EPC" surfaces use the lodged fragments. */ export async function resolvePropertyHeadline( propertyId: bigint,