mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-12 13:28:55 +00:00
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>
This commit is contained in:
parent
a89e6bfe30
commit
65db15d3c7
4 changed files with 56 additions and 4 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import RecommendationContainer from "@/app/components/building-passport/RecommendationContainer";
|
||||
import {
|
||||
getPropertyMeta,
|
||||
getPlanPropertyMeta,
|
||||
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 getPropertyMeta(params.propertyId);
|
||||
const propertyMeta = await getPlanPropertyMeta(params.propertyId);
|
||||
const [recommendations, planMeta, installedMeasures, scenarioData] =
|
||||
await Promise.all([
|
||||
getRecommendations(params.planId),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { getPlans, getPropertyMeta } from "../utils";
|
||||
import { getPlans, getPlanPropertyMeta } 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([
|
||||
getPropertyMeta(params.propertyId),
|
||||
getPlanPropertyMeta(params.propertyId),
|
||||
getPlans(params.propertyId),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import {
|
|||
ConditionReport,
|
||||
resolveConditionReport,
|
||||
resolvePropertyMeta,
|
||||
resolveEffectiveHeadline,
|
||||
} from "@/lib/services/epcSources";
|
||||
import { getRating, serializeBigInt } from "@/app/utils";
|
||||
import { eq, desc, and } from "drizzle-orm";
|
||||
|
|
@ -254,6 +255,27 @@ 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> {
|
||||
|
|
|
|||
|
|
@ -428,6 +428,36 @@ export async function resolvePropertyHeadline(
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 };
|
||||
}
|
||||
const baseline = await db.query.propertyBaselinePerformance.findFirst({
|
||||
columns: { effectiveSapScore: true, effectiveEpcBand: true },
|
||||
where: eq(propertyBaselinePerformance.propertyId, propertyId),
|
||||
});
|
||||
return {
|
||||
currentSapPoints: baseline?.effectiveSapScore ?? null,
|
||||
currentEpcRating: baseline?.effectiveEpcBand ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
/** The minimal EPC meta the `/api/property-meta` route embeds as `detailsEpc`. */
|
||||
export interface DetailsEpcMeta {
|
||||
currentEnergyDemand: number | null;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue