mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-06-30 12:55:02 +00:00
restructured the category 1 and category 2 hazards
This commit is contained in:
parent
ccc75ce059
commit
c265826a7c
2 changed files with 223 additions and 116 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue