mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-12 13:28:55 +00:00
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>
This commit is contained in:
parent
5dd2179bef
commit
b7cbd5c03f
2 changed files with 118 additions and 9 deletions
|
|
@ -316,26 +316,27 @@ export function formatGeneralFeatures(
|
|||
},
|
||||
{
|
||||
feature: "Number of storeys",
|
||||
description: conditionReportData.numberStoreys || "Unknown",
|
||||
description: conditionReportData.numberStoreys ?? "Unknown",
|
||||
},
|
||||
];
|
||||
|
||||
const generealFeatures: GeneralFeature[] = [
|
||||
{
|
||||
feature: "Floor Height",
|
||||
description: conditionReportData.floorHeight || "Unknown",
|
||||
description: conditionReportData.floorHeight ?? "Unknown",
|
||||
},
|
||||
{
|
||||
feature: "Number of heated rooms",
|
||||
description: conditionReportData.numberHeatedRooms || "Unknown",
|
||||
description: conditionReportData.numberHeatedRooms ?? "Unknown",
|
||||
},
|
||||
{
|
||||
feature: "Number of open fire places",
|
||||
description: conditionReportData.numberOpenFireplaces || "Unknown",
|
||||
// ?? so a genuine 0 (no fireplaces) shows as 0, not "Unknown".
|
||||
description: conditionReportData.numberOpenFireplaces ?? "Unknown",
|
||||
},
|
||||
{
|
||||
feature: "Number of extensions",
|
||||
description: conditionReportData.numberExtensions || "Unknown",
|
||||
description: conditionReportData.numberExtensions ?? "Unknown",
|
||||
},
|
||||
{
|
||||
feature: "Mains gas",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import {
|
|||
epcProperty,
|
||||
epcEnergyElement,
|
||||
epcFlatDetails,
|
||||
epcBuildingPart,
|
||||
epcFloorDimension,
|
||||
propertyBaselinePerformance,
|
||||
propertyDetailsEpc,
|
||||
property,
|
||||
|
|
@ -235,7 +237,7 @@ async function resolveNewConditionReport(
|
|||
|
||||
if (!chosen) return null;
|
||||
|
||||
const [elements, flat, baseline] = await Promise.all([
|
||||
const [elements, flat, baseline, buildingParts] = await Promise.all([
|
||||
db.query.epcEnergyElement.findMany({
|
||||
where: eq(epcEnergyElement.epcPropertyId, chosen.id),
|
||||
}),
|
||||
|
|
@ -245,8 +247,22 @@ async function resolveNewConditionReport(
|
|||
db.query.propertyBaselinePerformance.findFirst({
|
||||
where: eq(propertyBaselinePerformance.propertyId, propertyId),
|
||||
}),
|
||||
db.query.epcBuildingPart.findMany({
|
||||
columns: { id: true, identifier: true },
|
||||
where: eq(epcBuildingPart.epcPropertyId, chosen.id),
|
||||
}),
|
||||
]);
|
||||
|
||||
// Floor height: room height of the main building part (fall back to any part).
|
||||
const mainPart =
|
||||
buildingParts.find((b) => b.identifier === "main") ?? buildingParts[0];
|
||||
const mainFloor = mainPart
|
||||
? await db.query.epcFloorDimension.findFirst({
|
||||
columns: { roomHeightM: true },
|
||||
where: eq(epcFloorDimension.epcBuildingPartId, mainPart.id),
|
||||
})
|
||||
: null;
|
||||
|
||||
const byType = (t: string) => elements.find((el) => el.elementType === t);
|
||||
const desc = (t: string) => byType(t)?.description ?? null;
|
||||
const rating = (t: string) => safeRating(byType(t)?.energyEfficiencyRating);
|
||||
|
|
@ -290,8 +306,9 @@ async function resolveNewConditionReport(
|
|||
flat?.heatLossCorridor != null ? flat.heatLossCorridor > 0 : null,
|
||||
unheatedCorridorLength: flat?.unheatedCorridorLengthM ?? null,
|
||||
numberStoreys: chosen.numberOfStoreys ?? null,
|
||||
// GAP: no single dwelling floor height (only per-building-part) — omitted.
|
||||
floorHeight: null,
|
||||
// Room height of the main building part (the new graph stores height per
|
||||
// building part rather than one dwelling value).
|
||||
floorHeight: mainFloor?.roomHeightM ?? null,
|
||||
numberHeatedRooms: chosen.heatedRoomsCount ?? null,
|
||||
numberOpenFireplaces: chosen.openChimneysCount ?? null,
|
||||
numberExtensions: chosen.extensionsCount ?? null,
|
||||
|
|
@ -465,6 +482,90 @@ export async function resolveDetailsEpcMeta(
|
|||
};
|
||||
}
|
||||
|
||||
// RdSAP code → label maps for the descriptive fields the new pipeline stores as
|
||||
// codes on epc_property / epc_building_part (the property-row text columns aren't
|
||||
// written for new-approach properties). Unknown codes fall back to the raw value.
|
||||
const PROPERTY_TYPE_LABELS: Record<string, string> = {
|
||||
"0": "House",
|
||||
"1": "Bungalow",
|
||||
"2": "Flat",
|
||||
"3": "Maisonette",
|
||||
"4": "Park home",
|
||||
};
|
||||
const BUILT_FORM_LABELS: Record<string, string> = {
|
||||
"1": "Detached",
|
||||
"2": "Semi-Detached",
|
||||
"3": "End-Terrace",
|
||||
"4": "Mid-Terrace",
|
||||
"5": "Enclosed End-Terrace",
|
||||
"6": "Enclosed Mid-Terrace",
|
||||
};
|
||||
const AGE_BAND_YEARS: Record<string, string> = {
|
||||
A: "before 1900",
|
||||
B: "1900–1929",
|
||||
C: "1930–1949",
|
||||
D: "1950–1966",
|
||||
E: "1967–1975",
|
||||
F: "1976–1982",
|
||||
G: "1983–1990",
|
||||
H: "1991–1995",
|
||||
I: "1996–2002",
|
||||
J: "2003–2006",
|
||||
K: "2007–2011",
|
||||
L: "2012 onwards",
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve the descriptive property fields (type / built form / age) from the
|
||||
* epc_property graph for new-approach properties, since the property-row text
|
||||
* columns aren't written. Returns nulls when there's no EPC graph.
|
||||
*/
|
||||
async function resolvePropertyDescriptors(
|
||||
propertyId: bigint,
|
||||
): Promise<{
|
||||
propertyType: string | null;
|
||||
builtForm: string | null;
|
||||
yearBuilt: string | null;
|
||||
}> {
|
||||
const ep =
|
||||
(await db.query.epcProperty.findFirst({
|
||||
columns: { id: true, propertyType: true, builtForm: true, dwellingType: true },
|
||||
where: and(
|
||||
eq(epcProperty.propertyId, propertyId),
|
||||
eq(epcProperty.source, "lodged"),
|
||||
),
|
||||
})) ??
|
||||
(await db.query.epcProperty.findFirst({
|
||||
columns: { id: true, propertyType: true, builtForm: true, dwellingType: true },
|
||||
where: and(
|
||||
eq(epcProperty.propertyId, propertyId),
|
||||
eq(epcProperty.source, "predicted"),
|
||||
),
|
||||
}));
|
||||
|
||||
if (!ep) {
|
||||
return { propertyType: null, builtForm: null, yearBuilt: null };
|
||||
}
|
||||
|
||||
const mainPart = await db.query.epcBuildingPart.findFirst({
|
||||
columns: { constructionAgeBand: true },
|
||||
where: eq(epcBuildingPart.epcPropertyId, ep.id),
|
||||
});
|
||||
|
||||
const propertyType =
|
||||
(ep.propertyType ? PROPERTY_TYPE_LABELS[ep.propertyType] : null) ??
|
||||
ep.dwellingType ??
|
||||
ep.propertyType ??
|
||||
null;
|
||||
const builtForm = ep.builtForm
|
||||
? BUILT_FORM_LABELS[ep.builtForm] ?? ep.builtForm
|
||||
: null;
|
||||
const ageBand = mainPart?.constructionAgeBand ?? null;
|
||||
const yearBuilt = ageBand ? AGE_BAND_YEARS[ageBand] ?? ageBand : null;
|
||||
|
||||
return { propertyType, builtForm, yearBuilt };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the full property-meta object (the property row + branched
|
||||
* `detailsEpc` + headline SAP/EPC). Shared by the `/api/property-meta` route and
|
||||
|
|
@ -501,13 +602,20 @@ export async function resolvePropertyMeta(propertyId: string) {
|
|||
|
||||
if (!propertyMeta) return null;
|
||||
|
||||
const [detailsEpc, headline] = await Promise.all([
|
||||
const newApproach = isNewApproach(propertyMeta.updatedAt);
|
||||
const [detailsEpc, headline, descriptors] = await Promise.all([
|
||||
resolveDetailsEpcMeta(propertyMeta.id, propertyMeta.updatedAt),
|
||||
resolvePropertyHeadline(propertyMeta.id, propertyMeta.updatedAt),
|
||||
newApproach
|
||||
? resolvePropertyDescriptors(propertyMeta.id)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
|
||||
return {
|
||||
...propertyMeta,
|
||||
propertyType: descriptors?.propertyType ?? propertyMeta.propertyType,
|
||||
builtForm: descriptors?.builtForm ?? propertyMeta.builtForm,
|
||||
yearBuilt: descriptors?.yearBuilt ?? propertyMeta.yearBuilt,
|
||||
currentSapPoints:
|
||||
headline.currentSapPoints ?? propertyMeta.currentSapPoints,
|
||||
currentEpcRating:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue