mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-12 13:28:55 +00:00
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>
This commit is contained in:
parent
65db15d3c7
commit
e51bf1d23f
4 changed files with 21 additions and 63 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import RecommendationContainer from "@/app/components/building-passport/RecommendationContainer";
|
||||
import {
|
||||
getPlanPropertyMeta,
|
||||
getPropertyMeta,
|
||||
getRecommendations,
|
||||
getPlanMeta,
|
||||
getInstalledMeasuresByUprn,
|
||||
|
|
@ -59,7 +59,7 @@ export default async function PlanDetail(props: {
|
|||
params: Promise<{ slug: string; propertyId: string; planId: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const propertyMeta = await getPlanPropertyMeta(params.propertyId);
|
||||
const propertyMeta = await getPropertyMeta(params.propertyId);
|
||||
const [recommendations, planMeta, installedMeasures, scenarioData] =
|
||||
await Promise.all([
|
||||
getRecommendations(params.planId),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getPlans, getPlanPropertyMeta } from "../utils";
|
||||
import { getPlans, getPropertyMeta } from "../utils";
|
||||
import { sapToEpc } from "@/app/utils";
|
||||
import PlanCard from "./PlanCard";
|
||||
import PlanHeroCard from "./PlanHeroCard";
|
||||
|
|
@ -9,7 +9,7 @@ export default async function RecommendationPlans(props: {
|
|||
}) {
|
||||
const params = await props.params;
|
||||
const [propertyMeta, plans] = await Promise.all([
|
||||
getPlanPropertyMeta(params.propertyId),
|
||||
getPropertyMeta(params.propertyId),
|
||||
getPlans(params.propertyId),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import {
|
|||
ConditionReport,
|
||||
resolveConditionReport,
|
||||
resolvePropertyMeta,
|
||||
resolveEffectiveHeadline,
|
||||
} from "@/lib/services/epcSources";
|
||||
import { getRating, serializeBigInt } from "@/app/utils";
|
||||
import { eq, desc, and } from "drizzle-orm";
|
||||
|
|
@ -255,27 +254,6 @@ export async function getPropertyMeta(
|
|||
return JSON.parse(JSON.stringify(meta, serializeBigInt)) as PropertyMeta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property meta for the plan pages. Identical to `getPropertyMeta` except the
|
||||
* "current" SAP/EPC is the effective (modelling) baseline the plans were scored
|
||||
* against, not the lodged EPC — see `resolveEffectiveHeadline`. Legacy
|
||||
* properties and backfill gaps fall back to the lodged/row values.
|
||||
*/
|
||||
export async function getPlanPropertyMeta(
|
||||
propertyId: string
|
||||
): Promise<PropertyMeta> {
|
||||
const meta = await getPropertyMeta(propertyId);
|
||||
const effective = await resolveEffectiveHeadline(
|
||||
BigInt(propertyId),
|
||||
meta.updatedAt
|
||||
);
|
||||
return {
|
||||
...meta,
|
||||
currentSapPoints: effective.currentSapPoints ?? meta.currentSapPoints,
|
||||
currentEpcRating: effective.currentEpcRating ?? meta.currentEpcRating,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getConditionReport(
|
||||
propertyId: string
|
||||
): Promise<ConditionReport> {
|
||||
|
|
|
|||
|
|
@ -320,9 +320,11 @@ async function resolveNewConditionReport(
|
|||
// GAP: no energy tariff in the new graph — omitted.
|
||||
energyTariff: null,
|
||||
currentEnergyDemand: sumKwh,
|
||||
// Effective (re-baselined) baseline so the assessment view agrees with the
|
||||
// headline SAP/EPC and the plan maths — see resolvePropertyHeadline.
|
||||
primaryEnergyConsumption:
|
||||
baseline?.lodgedPrimaryEnergyIntensityKwhPerM2Yr ?? null,
|
||||
co2Emissions: baseline?.lodgedCo2EmissionsTPerYr ?? null,
|
||||
baseline?.effectivePrimaryEnergyIntensityKwhPerM2Yr ?? null,
|
||||
co2Emissions: baseline?.effectiveCo2EmissionsTPerYr ?? null,
|
||||
heatingEnergyCostCurrent: baseline?.heatingCostGbp ?? null,
|
||||
hotWaterEnergyCostCurrent: baseline?.hotWaterCostGbp ?? null,
|
||||
lightingEnergyCostCurrent: baseline?.lightingCostGbp ?? null,
|
||||
|
|
@ -405,45 +407,23 @@ export async function resolveConditionReport(
|
|||
/**
|
||||
* Headline SAP score + EPC band for the property meta. New-approach properties
|
||||
* don't have `current_sap_points` / `current_epc_rating` written on the row yet,
|
||||
* so we fall back to the lodged baseline (property_baseline_performance). Returns
|
||||
* nulls for legacy properties, signalling the caller to keep the row values.
|
||||
* so we fall back to the modelling baseline (property_baseline_performance).
|
||||
* Returns nulls for legacy properties, signalling the caller to keep the row
|
||||
* values.
|
||||
*
|
||||
* Uses lodged_* (gov-registry actual) to match the rest of the EPC reads; swap to
|
||||
* effective_* for the modelling-adjusted baseline.
|
||||
* Uses `effective_*` — the re-baselined figure the modelling (and therefore the
|
||||
* plans) was scored against. The per-property building-passport views (overview,
|
||||
* assessment, plans) must agree with the plan maths: re-baselining can move the
|
||||
* 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.
|
||||
*/
|
||||
export async function resolvePropertyHeadline(
|
||||
propertyId: bigint,
|
||||
updatedAt: Date | string | null | undefined,
|
||||
): Promise<{ currentSapPoints: number | null; currentEpcRating: string | null }> {
|
||||
if (!isNewApproach(updatedAt)) {
|
||||
return { currentSapPoints: null, currentEpcRating: null };
|
||||
}
|
||||
const baseline = await db.query.propertyBaselinePerformance.findFirst({
|
||||
columns: { lodgedSapScore: true, lodgedEpcBand: true },
|
||||
where: eq(propertyBaselinePerformance.propertyId, propertyId),
|
||||
});
|
||||
return {
|
||||
currentSapPoints: baseline?.lodgedSapScore ?? null,
|
||||
currentEpcRating: baseline?.lodgedEpcBand ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Headline SAP score + EPC band for the *plan* pages. Plans are scored against
|
||||
* the modelling baseline (`effective_*` on property_baseline_performance), which
|
||||
* re-baselining can move away from the lodged (gov-registry) EPC we show
|
||||
* everywhere else. Using the lodged figure as a plan's "current" would
|
||||
* misrepresent the uplift — e.g. lodged C/71 vs an effective baseline of D/64
|
||||
* with a post-retrofit C/69.6 reads as a *downgrade* (71 → 69.6) instead of the
|
||||
* real D → C improvement. So plan pages take the effective baseline as "current".
|
||||
*
|
||||
* Returns nulls for legacy properties (no effective/lodged split — keep the
|
||||
* property-row values) and when no baseline row exists yet (backfill gap — fall
|
||||
* back to the lodged headline).
|
||||
*/
|
||||
export async function resolveEffectiveHeadline(
|
||||
propertyId: bigint,
|
||||
updatedAt: Date | string | null | undefined,
|
||||
): Promise<{ currentSapPoints: number | null; currentEpcRating: string | null }> {
|
||||
if (!isNewApproach(updatedAt)) {
|
||||
return { currentSapPoints: null, currentEpcRating: null };
|
||||
|
|
@ -500,7 +480,7 @@ export async function resolveDetailsEpcMeta(
|
|||
|
||||
return {
|
||||
currentEnergyDemand,
|
||||
co2Emissions: baseline?.lodgedCo2EmissionsTPerYr ?? null,
|
||||
co2Emissions: baseline?.effectiveCo2EmissionsTPerYr ?? null,
|
||||
estimated: !lodged && !!predicted,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue