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>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-23 11:24:25 +00:00
parent a1ba5b1e0d
commit 9750770c7c

View file

@ -253,15 +253,18 @@ async function resolveNewConditionReport(
}),
]);
// Floor height: room height of the main building part (fall back to any part).
// Floor height + storey count from the main building part's floor dimensions
// (fall back to any part). Each floor dimension is one storey of that part.
const mainPart =
buildingParts.find((b) => b.identifier === "main") ?? buildingParts[0];
const mainFloor = mainPart
? await db.query.epcFloorDimension.findFirst({
const mainFloors = mainPart
? await db.query.epcFloorDimension.findMany({
columns: { roomHeightM: true },
where: eq(epcFloorDimension.epcBuildingPartId, mainPart.id),
})
: null;
: [];
const floorHeight = mainFloors[0]?.roomHeightM ?? null;
const storeyCountFromFloors = mainFloors.length || null;
const byType = (t: string) => elements.find((el) => el.elementType === t);
const desc = (t: string) => byType(t)?.description ?? null;
@ -305,10 +308,11 @@ async function resolveNewConditionReport(
heatLossCorridor:
flat?.heatLossCorridor != null ? flat.heatLossCorridor > 0 : null,
unheatedCorridorLength: flat?.unheatedCorridorLengthM ?? null,
numberStoreys: chosen.numberOfStoreys ?? null,
// Storeys: epc_property value if present, else the main part's floor count.
numberStoreys: chosen.numberOfStoreys ?? storeyCountFromFloors,
// Room height of the main building part (the new graph stores height per
// building part rather than one dwelling value).
floorHeight: mainFloor?.roomHeightM ?? null,
floorHeight,
numberHeatedRooms: chosen.heatedRoomsCount ?? null,
numberOpenFireplaces: chosen.openChimneysCount ?? null,
numberExtensions: chosen.extensionsCount ?? null,
@ -528,17 +532,25 @@ async function resolvePropertyDescriptors(
propertyType: string | null;
builtForm: string | null;
yearBuilt: string | null;
numberOfRooms: number | null;
}> {
const cols = {
id: true,
propertyType: true,
builtForm: true,
dwellingType: true,
habitableRoomsCount: true,
} as const;
const ep =
(await db.query.epcProperty.findFirst({
columns: { id: true, propertyType: true, builtForm: true, dwellingType: true },
columns: cols,
where: and(
eq(epcProperty.propertyId, propertyId),
eq(epcProperty.source, "lodged"),
),
})) ??
(await db.query.epcProperty.findFirst({
columns: { id: true, propertyType: true, builtForm: true, dwellingType: true },
columns: cols,
where: and(
eq(epcProperty.propertyId, propertyId),
eq(epcProperty.source, "predicted"),
@ -546,7 +558,12 @@ async function resolvePropertyDescriptors(
}));
if (!ep) {
return { propertyType: null, builtForm: null, yearBuilt: null };
return {
propertyType: null,
builtForm: null,
yearBuilt: null,
numberOfRooms: null,
};
}
const mainPart = await db.query.epcBuildingPart.findFirst({
@ -566,7 +583,12 @@ async function resolvePropertyDescriptors(
const ageBand = mainPart?.constructionAgeBand ?? null;
const yearBuilt = ageBand ? AGE_BAND_YEARS[ageBand] ?? ageBand : null;
return { propertyType, builtForm, yearBuilt };
return {
propertyType,
builtForm,
yearBuilt,
numberOfRooms: ep.habitableRoomsCount ?? null,
};
}
/**
@ -619,6 +641,7 @@ export async function resolvePropertyMeta(propertyId: string) {
propertyType: descriptors?.propertyType ?? propertyMeta.propertyType,
builtForm: descriptors?.builtForm ?? propertyMeta.builtForm,
yearBuilt: descriptors?.yearBuilt ?? propertyMeta.yearBuilt,
numberOfRooms: descriptors?.numberOfRooms ?? propertyMeta.numberOfRooms,
currentSapPoints:
headline.currentSapPoints ?? propertyMeta.currentSapPoints,
currentEpcRating: