restructured the category 1 and category 2 hazards

This commit is contained in:
Khalim Conn-Kowlessar 2025-08-26 18:06:17 +00:00
parent ccc75ce059
commit c265826a7c
2 changed files with 223 additions and 116 deletions

View file

@ -18,6 +18,7 @@ import {
} from "@/app/shadcn_components/ui/accordion";
import clsx from "clsx";
import { AlertTriangle } from "lucide-react";
import { PropertyDetailsEpc } from "@/app/db/schema/property";
function ChecklistItem({
@ -129,13 +130,17 @@ function getRecommendedOccupants(bedrooms: number): number {
export default function ConditionReport({
conditionReport,
totalFloorArea
totalFloorArea,
currentSapPoints,
conditionData,
}: {
conditionReport: {
rooms: Record<string, any>;
[key: string]: any;
},
totalFloorArea: number;
currentSapPoints: number;
conditionData: PropertyDetailsEpc
}) {
// Documentation on decent home standards can be found here:
@ -179,75 +184,108 @@ export default function ConditionReport({
b?.room_info?.overall_condition_of_the_room === "Good"
)
const roomsWithDefects = allRoomData.filter(
([, room]: any) => room.room_info?.does_the_room_have_any_defects === "Yes"
);
const roomsWithDefects = allRoomData.filter(
([, room]: any) => room.room_info?.does_the_room_have_any_defects === "Yes"
);
const roomsWithDamp = allRoomData.filter(
([, room]: any) =>
room.room_info?.ventilation_info
?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
);
const roomsWithDamp = allRoomData.filter(
([, room]: any) =>
room.room_info?.ventilation_info
?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
);
const roomsWithBadWindows = allRoomData.filter(
([, room]: any) => {
const wi = room.room_info?.windows_info;
return wi?.does_the_room_have_any_windows && wi.condition_of_the_windows !== "Good condition";
}
);
// Check if the property has adequate space
// We've seen a case where the number of adult occupants + child occupants is greater than the total_number_of_occupants so we
// take the biggest of the two
const totalOccupants = conditionReport.occupant_info?.total_number_of_occupants ?? 0;
const totalAdults = conditionReport.occupant_info?.no_of_adult_occupants ?? 0;
const totalChildren = conditionReport.occupant_info?.no_of_child_occupants ?? 0;
const numberOfBedrooms = Array.isArray(rooms.bedrooms)
? rooms.bedrooms.length
: 0;
const occupantsToUse = Math.max(totalAdults + totalChildren, totalOccupants);
const maxOccupants = getRecommendedOccupants(numberOfBedrooms);
const areaPerPerson = totalFloorArea / occupantsToUse;
const hasSufficientSpace = (occupantsToUse <= maxOccupants) && (areaPerPerson >= 20);
// Category 1 Hazard structural
const frontElevation = conditionReport.access_and_elevations?.external_elevation_front?.external_elevation;
const elevationEntries: [string, any][] = [
["Front Elevation", frontElevation],
["Back Elevation", conditionReport.access_and_elevations?.external_elevation_back?.external_elevation],
["Gable One", conditionReport.access_and_elevations?.external_elevation_gable_one?.external_elevation],
["Gable Two", conditionReport.access_and_elevations?.external_elevation_gable_two?.external_elevation],
];
// If a wall inherits front elevation, use the front's data
const applyFrontDefaults = (label: string, elevationKey: string): [string, any] => {
const section = conditionReport.access_and_elevations?.[elevationKey];
if (section?.do_all_answers_for_the_front_elevation_apply_to_this_wall) {
return [label, frontElevation];
const roomsWithBadWindows = allRoomData.filter(
([, room]: any) => {
const wi = room.room_info?.windows_info;
return wi?.does_the_room_have_any_windows && wi.condition_of_the_windows !== "Good condition";
}
return [label, section?.external_elevation ?? null];
};
);
const allElevations: [string, any][] = [
["Front Elevation", frontElevation],
applyFrontDefaults("Back Elevation", "external_elevation_back"),
applyFrontDefaults("Gable One", "external_elevation_gable_one"),
applyFrontDefaults("Gable Two", "external_elevation_gable_two"),
];
// Check if the property has adequate space
// We've seen a case where the number of adult occupants + child occupants is greater than the total_number_of_occupants so we
// take the biggest of the two
const totalOccupants = conditionReport.occupant_info?.total_number_of_occupants ?? 0;
const totalAdults = conditionReport.occupant_info?.no_of_adult_occupants ?? 0;
const totalChildren = conditionReport.occupant_info?.no_of_child_occupants ?? 0;
const numberOfBedrooms = Array.isArray(rooms.bedrooms)
? rooms.bedrooms.length
: 0;
const elevationsWithIssues = allElevations.filter(([_, elevation]) =>
elevation &&
(
elevation.does_any_structural_defect_need_resolving_before_retrofit === true ||
elevation.are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework === true ||
elevation.are_there_any_visible_signs_of_movement === true ||
elevation.are_there_any_visible_signs_of_cracking_to_the_existing_external_finish === true
)
);
const occupantsToUse = Math.max(totalAdults + totalChildren, totalOccupants);
const maxOccupants = getRecommendedOccupants(numberOfBedrooms);
const areaPerPerson = totalFloorArea / occupantsToUse;
const hasSufficientSpace = (occupantsToUse <= maxOccupants) && (areaPerPerson >= 20);
// Category 1 Hazard structural
const frontElevation = conditionReport.access_and_elevations?.external_elevation_front?.external_elevation;
const elevationEntries: [string, any][] = [
["Front Elevation", frontElevation],
["Back Elevation", conditionReport.access_and_elevations?.external_elevation_back?.external_elevation],
["Gable One", conditionReport.access_and_elevations?.external_elevation_gable_one?.external_elevation],
["Gable Two", conditionReport.access_and_elevations?.external_elevation_gable_two?.external_elevation],
];
// If a wall inherits front elevation, use the front's data
const applyFrontDefaults = (label: string, elevationKey: string): [string, any] => {
const section = conditionReport.access_and_elevations?.[elevationKey];
if (section?.do_all_answers_for_the_front_elevation_apply_to_this_wall) {
return [label, frontElevation];
}
return [label, section?.external_elevation ?? null];
};
const allElevations: [string, any][] = [
["Front Elevation", frontElevation],
applyFrontDefaults("Back Elevation", "external_elevation_back"),
applyFrontDefaults("Gable One", "external_elevation_gable_one"),
applyFrontDefaults("Gable Two", "external_elevation_gable_two"),
];
const elevationsWithIssues = allElevations.filter(([_, elevation]) =>
elevation &&
(
elevation.does_any_structural_defect_need_resolving_before_retrofit === true ||
elevation.are_there_any_signs_of_water_penetration_caused_by_failed_rainwater_goods_or_pipework === true ||
elevation.are_there_any_visible_signs_of_movement === true ||
elevation.are_there_any_visible_signs_of_cracking_to_the_existing_external_finish === true
)
);
// Thermal comfort
const meetsSapThreshold = currentSapPoints >= 35;
const heatingType = conditionData.heating?.toLowerCase();
const hasEfficientHeating =
heatingType?.includes("gas") ||
heatingType?.includes("oil") ||
heatingType?.includes("storage") ||
heatingType?.includes("warm air") ||
heatingType?.includes("underfloor") ||
heatingType?.includes("lpg") ||
heatingType?.includes("solid fuel");
const loftInsulationRating = conditionData.roofRating; // assume rating 3+ is ≥50mm
const wallInsulationRating = conditionData.wallsRating; // assume rating 3+ is insulated
let insulationIsAdequate = false;
if (heatingType?.includes("gas") || heatingType?.includes("oil")) {
insulationIsAdequate =
(loftInsulationRating != null && loftInsulationRating >= 3) ||
(wallInsulationRating != null && wallInsulationRating >= 3);
} else if (
heatingType?.includes("storage") ||
heatingType?.includes("lpg") ||
heatingType?.includes("solid fuel")
) {
insulationIsAdequate =
(loftInsulationRating != null && loftInsulationRating >= 4) &&
(wallInsulationRating != null && wallInsulationRating >= 3);
}
const thermalComfortOk = hasEfficientHeating && insulationIsAdequate;
return (
<div className="space-y-6 mt-8">
@ -255,58 +293,119 @@ const elevationsWithIssues = allElevations.filter(([_, elevation]) =>
<CardHeader className="text-lg font-semibold text-brandblue">
Decent Homes Checklist
</CardHeader>
<CardContent className="space-y-3">
<ChecklistItem
label={
elevationsWithIssues.length > 0
? "Structural issues identified"
: "No major structural issues identified"
}
passed={elevationsWithIssues.length === 0}
alert={elevationsWithIssues.length > 0}
roomsWithIssues={elevationsWithIssues}
/>
<ChecklistItem
label={roomsWithDamp ? "Signs of damp or mould present": "No signs of damp or mould"}
passed={roomsWithDamp.length === 0}
alert={roomsWithDamp.length > 0}
roomsWithIssues={roomsWithDamp}
/>
<ChecklistItem
label={hasDefects ? "Room defects present" : "No room defects present"}
passed={!hasDefects}
alert={hasDefects}
roomsWithIssues={roomsWithDefects}
/>
<ChecklistItem
label={heatingWorking ? "Heating system operational" : "Heating system not operational"}
passed={heatingWorking}
alert={!heatingWorking}
/>
<ChecklistItem
label={windowsOk ? "Windows in good condition" : "Windows not in good condition"}
passed={windowsOk}
alert={!windowsOk}
roomsWithIssues={roomsWithBadWindows}
/>
<ChecklistItem
label={kitchenOk ? "Kitchen in good condition" : "Kitchen not in good condition"}
passed={kitchenOk}
alert={!kitchenOk}
/>
<ChecklistItem
label={bathroomsOk ? "Bathrooms in good condition" : "Bathrooms not in good condition"}
passed={bathroomsOk}
alert={!bathroomsOk}
/>
<ChecklistItem
label="Sufficient space for number of occupants"
passed={hasSufficientSpace}
alert={!hasSufficientSpace}
note={`${occupantsToUse} occupants, ${numberOfBedrooms} bedrooms. ${areaPerPerson}m² per person`}
/>
</CardContent>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Category 1 Hazards */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground mb-1 uppercase tracking-wide">
Category 1 Hazards
</h3>
<ChecklistItem
label={
elevationsWithIssues.length > 0
? "Structural issues identified"
: "No major structural issues identified"
}
passed={elevationsWithIssues.length === 0}
alert={elevationsWithIssues.length > 0}
roomsWithIssues={elevationsWithIssues}
/>
<ChecklistItem
label={
roomsWithDamp.length > 0
? "Signs of damp or mould present"
: "No signs of damp or mould"
}
passed={roomsWithDamp.length === 0}
alert={roomsWithDamp.length > 0}
roomsWithIssues={roomsWithDamp}
/>
<ChecklistItem
label={
heatingWorking
? "Heating system operational"
: "Heating system not operational"
}
passed={heatingWorking}
alert={!heatingWorking}
/>
<ChecklistItem
label={meetsSapThreshold ? "SAP rating meets minimum threshold (≥ 35)" : "SAP rating below recommended minimum"}
passed={meetsSapThreshold}
alert={!meetsSapThreshold}
note={`SAP Points: ${currentSapPoints}`}
/>
<ChecklistItem
label={thermalComfortOk ? "Thermal comfort conditions met" : "Thermal comfort conditions not met"}
passed={thermalComfortOk}
alert={!thermalComfortOk}
note={
`Heating: ${conditionData.heating}; ` +
`Roof Rating: ${conditionData.roofRating ?? "N/A"}; ` +
`Wall Rating: ${conditionData.wallsRating ?? "N/A"}`
}
/>
</div>
{/* Category 2 Hazards */}
<div className="space-y-3">
<h3 className="text-sm font-semibold text-muted-foreground mb-1 uppercase tracking-wide">
Category 2 Hazards
</h3>
<ChecklistItem
label={hasDefects ? "Room defects present" : "No room defects present"}
passed={!hasDefects}
alert={hasDefects}
roomsWithIssues={roomsWithDefects}
/>
<ChecklistItem
label={
windowsOk
? "Windows in good condition"
: "Windows not in good condition"
}
passed={windowsOk}
alert={!windowsOk}
roomsWithIssues={roomsWithBadWindows}
/>
<ChecklistItem
label={
kitchenOk
? "Kitchen in good condition"
: "Kitchen not in good condition"
}
passed={kitchenOk}
alert={!kitchenOk}
/>
<ChecklistItem
label={
bathroomsOk
? "Bathrooms in good condition"
: "Bathrooms not in good condition"
}
passed={bathroomsOk}
alert={!bathroomsOk}
/>
<ChecklistItem
label="Sufficient space for number of occupants"
passed={hasSufficientSpace}
alert={!hasSufficientSpace}
note={`${occupantsToUse} occupants, ${numberOfBedrooms} bedrooms. ${areaPerPerson}m² per person`}
/>
</div>
</div>
</CardContent>
</Card>
</div>

View file

@ -144,6 +144,7 @@ export default async function PreAssessmentReport(
}
// console.log("conditionReport", conditionReport.rooms.utility)
console.log("Condition data", propertyMeta)
const nonIntrusiveSurvey = await getNonIntrusiveSurvey(propertyMeta.uprn);
@ -167,6 +168,7 @@ export default async function PreAssessmentReport(
<EpcCard
epcRating={propertyMeta.currentEpcRating}
fullMargin={false}
sap={String(propertyMeta.currentSapPoints)}
/>
<PropertyDetailsCard
@ -178,7 +180,13 @@ export default async function PreAssessmentReport(
</div>
{
Object.keys(conditionReportMeta).length > 0 && <ConditionReport conditionReport={conditionReport} totalFloorArea={conditionReportData.totalFloorArea}/>
Object.keys(conditionReportMeta).length > 0 &&
<ConditionReport
conditionReport={conditionReport}
totalFloorArea={conditionReportData.totalFloorArea}
currentSapPoints={propertyMeta.currentSapPoints}
conditionData={conditionReportData}
/>
}
{nonIntrusiveSurvey && (