added fundamental layout for decent homes moniroting

This commit is contained in:
Khalim Conn-Kowlessar 2025-08-26 21:36:14 +00:00
parent c265826a7c
commit 45f5634f03
7 changed files with 218 additions and 226 deletions

View file

@ -13,9 +13,13 @@
"customizations": {
"vscode": {
"settings": {
"files.defaultWorkspace": "/workspaces/assessment-model"
"files.defaultWorkspace": "/workspaces/assessment-model",
"editor.formatOnSave": true,
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"extensions": [
"esbenp.prettier-vscode"
]
}
}

168
package-lock.json generated
View file

@ -14,7 +14,7 @@
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.1.0",
@ -39,7 +39,6 @@
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.3",
"esbuild": "^0.25.8",
"eslint": "8.41.0",
"eslint-config-next": "13.4.3",
"lucide-react": "^0.233.0",
"next": "^15.4.2",
@ -56,6 +55,7 @@
"tailwindcss-animate": "^1.0.6",
"tsx": "^4.20.3",
"typescript": "5.0.4",
"vaul": "^1.1.2",
"xlsx": "^0.18.5",
"zod": "^3.23.8"
},
@ -66,6 +66,8 @@
"cypress": "^14.5.3",
"cypress-social-logins": "^1.14.1",
"dotenv": "^16.3.1",
"eslint": "^8.57.1",
"prettier": "^3.6.2",
"start-server-and-test": "^2.0.0"
}
},
@ -1733,9 +1735,9 @@
}
},
"node_modules/@eslint/js": {
"version": "8.41.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz",
"integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==",
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"license": "MIT",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -1850,13 +1852,13 @@
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
"integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
"deprecated": "Use @eslint/config-array instead",
"license": "Apache-2.0",
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.2",
"@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
@ -2942,20 +2944,20 @@
}
},
"node_modules/@radix-ui/react-dialog": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz",
"integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==",
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
"integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.2",
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.10",
"@radix-ui/react-focus-guards": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.11",
"@radix-ui/react-focus-guards": "1.1.3",
"@radix-ui/react-focus-scope": "1.1.7",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-portal": "1.1.9",
"@radix-ui/react-presence": "1.1.4",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
@ -2977,6 +2979,78 @@
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
"license": "MIT"
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
"integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-use-callback-ref": "1.1.1",
"@radix-ui/react-use-escape-keydown": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
"integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
"integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-use-layout-effect": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
@ -5480,6 +5554,12 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
"node_modules/@unrs/resolver-binding-android-arm-eabi": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
@ -8145,28 +8225,29 @@
}
},
"node_modules/eslint": {
"version": "8.41.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz",
"integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==",
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
"integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
"deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.4.0",
"@eslint/eslintrc": "^2.0.3",
"@eslint/js": "8.41.0",
"@humanwhocodes/config-array": "^0.11.8",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
"@eslint/js": "8.57.1",
"@humanwhocodes/config-array": "^0.13.0",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0",
"@ungap/structured-clone": "^1.2.0",
"ajv": "^6.12.4",
"chalk": "^4.0.0",
"cross-spawn": "^7.0.2",
"debug": "^4.3.2",
"doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.2.0",
"eslint-visitor-keys": "^3.4.1",
"espree": "^9.5.2",
"eslint-scope": "^7.2.2",
"eslint-visitor-keys": "^3.4.3",
"espree": "^9.6.1",
"esquery": "^1.4.2",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@ -8176,7 +8257,6 @@
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
"import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
@ -8186,9 +8266,8 @@
"lodash.merge": "^4.6.2",
"minimatch": "^3.1.2",
"natural-compare": "^1.4.0",
"optionator": "^0.9.1",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"bin": {
@ -11735,6 +11814,22 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
@ -14075,6 +14170,19 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/vaul": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz",
"integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-dialog": "^1.1.1"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc"
}
},
"node_modules/verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",

View file

@ -20,7 +20,7 @@
"@hookform/resolvers": "^3.9.1",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-hover-card": "^1.0.6",
"@radix-ui/react-label": "^2.1.0",
@ -45,7 +45,6 @@
"drizzle-kit": "^0.31.4",
"drizzle-orm": "^0.44.3",
"esbuild": "^0.25.8",
"eslint": "8.41.0",
"eslint-config-next": "13.4.3",
"lucide-react": "^0.233.0",
"next": "^15.4.2",
@ -62,6 +61,7 @@
"tailwindcss-animate": "^1.0.6",
"tsx": "^4.20.3",
"typescript": "5.0.4",
"vaul": "^1.1.2",
"xlsx": "^0.18.5",
"zod": "^3.23.8"
},
@ -72,6 +72,8 @@
"cypress": "^14.5.3",
"cypress-social-logins": "^1.14.1",
"dotenv": "^16.3.1",
"eslint": "^8.57.1",
"prettier": "^3.6.2",
"start-server-and-test": "^2.0.0"
}
}

View file

@ -70,15 +70,15 @@ export function Toolbar({ propertyId, portfolioId, conditionReport }: ToolbarPro
</NavigationMenuLink>
);
const energyAssessmentsReportButton = (
<NavigationMenuLink
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
href={`/portfolio/${portfolioId}/building-passport/${propertyId}/energy-assessment`}
>
<BoltIcon className="h-4 w-4 mr-2" />
Energy Assessment
</NavigationMenuLink>
);
// const energyAssessmentsReportButton = (
// <NavigationMenuLink
// className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
// href={`/portfolio/${portfolioId}/building-passport/${propertyId}/energy-assessment`}
// >
// <BoltIcon className="h-4 w-4 mr-2" />
// Energy Assessment
// </NavigationMenuLink>
// );
const documentsButton = (
<NavigationMenuLink

View file

@ -4,7 +4,7 @@ import {
Cog6ToothIcon,
BuildingOfficeIcon,
ChartBarIcon,
WrenchScrewdriverIcon,
HomeModernIcon,
} from "@heroicons/react/24/outline";
import {
NavigationMenu,
@ -43,8 +43,11 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) {
router.push(`/portfolio/${portfolioId}/summary`);
}
function handleClickMeasures() {
router.push(`/portfolio/${portfolioId}/measures`);
// function handleClickMeasures() {
// router.push(`/portfolio/${portfolioId}/measures`);
// }
function handleClickDecentHomes() {
router.push(`/portfolio/${portfolioId}/decent-homes`);
}
const [modalIsOpen, setModalIsOpen] = useState(false);
@ -66,7 +69,15 @@ export function Toolbar({ portfolioId, scenarios }: ToolbarProps) {
onClick={handleClickSummary}
>
<ChartBarIcon className="h-4 w-4 mr-2" />
Summary
Retrofit Summary
</NavigationMenuItem>
<NavigationMenuItem
className={navigationMenuTriggerStyle() + " ml-3 mr-2"}
onClick={handleClickDecentHomes}
>
<HomeModernIcon className="h-4 w-4 mr-2" />
Decent Homes
</NavigationMenuItem>
{/* <NavigationMenuItem

View file

@ -19,7 +19,18 @@ import {
import clsx from "clsx";
import { AlertTriangle } from "lucide-react";
import { PropertyDetailsEpc } from "@/app/db/schema/property";
import {
getAllRoomData,
getRoomsWithDamp,
getRoomsWithDefects,
getRoomsWithBadWindows,
areAllWindowsOk,
getElevationsWithIssues,
hasSufficientSpace,
hasEfficientHeatingSystem,
isInsulationAdequate,
meetsSapThreshold,
} from "./decent_homes_utils";
function ChecklistItem({
label,
@ -120,14 +131,6 @@ function formatRoomName(name: string) {
.replace("Room Info", "Room");
}
function getRecommendedOccupants(bedrooms: number): number {
if (bedrooms <= 0) return 0;
if (bedrooms === 1) return 2;
if (bedrooms === 2) return 4;
if (bedrooms === 3) return 6;
return 7; // 4 or more
}
export default function ConditionReport({
conditionReport,
totalFloorArea,
@ -140,152 +143,35 @@ export default function ConditionReport({
},
totalFloorArea: number;
currentSapPoints: number;
conditionData: PropertyDetailsEpc
conditionData: PropertyDetailsEpc;
}) {
const allRoomData = getAllRoomData(conditionReport.rooms);
const roomsWithDamp = getRoomsWithDamp(allRoomData);
const roomsWithDefects = getRoomsWithDefects(allRoomData);
const roomsWithBadWindows = getRoomsWithBadWindows(allRoomData);
const windowsOk = areAllWindowsOk(allRoomData);
// Documentation on decent home standards can be found here:
// https://assets.publishing.service.gov.uk/media/5a7968b740f0b63d72fc5926/138355.pdf
const elevationsWithIssues = getElevationsWithIssues(conditionReport.access_and_elevations);
const rooms = conditionReport.rooms;
const {
isSufficient: enoughSpace,
occupantsToUse,
numberOfBedrooms,
areaPerPerson,
} = hasSufficientSpace(conditionReport.occupant_info, totalFloorArea, allRoomData);
const allRoomData = [
...Object.entries(rooms).filter(([k, v]) => v?.room_info),
...(rooms.bedrooms || []).map((b: any, i: number) => ["Bedroom " + (i + 1), b]),
...(rooms.bathrooms || []).map((b: any, i: number) => ["Bathroom " + (i + 1), b]),
];
const sapOk = meetsSapThreshold(currentSapPoints);
const efficientHeating = hasEfficientHeatingSystem(conditionData.heating || "");
const insulationOk = isInsulationAdequate(conditionData.heating || "", conditionData.roofRating, conditionData.wallsRating);
const thermalComfortOk = efficientHeating && insulationOk;
const hasDampIssues = allRoomData.some(
([, room]: any) =>
room.room_info?.ventilation_info
?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
);
const hasDefects = allRoomData.some(
([, room]: any) => room.room_info?.does_the_room_have_any_defects === "Yes"
);
const windowsOk = allRoomData.every(([, room]: any) => {
const wi = room.room_info?.windows_info;
return wi?.does_the_room_have_any_windows
? wi?.condition_of_the_windows === "Good condition"
: true;
});
const heatingWorking =
conditionReport.heating_system?.general_condition
?.is_the_heating_system_in_working_order === true;
const kitchenOk = rooms.kitchen?.room_info?.overall_condition_of_the_room === "Good";
const bathroomsOk = Array.isArray(rooms.bathrooms) &&
rooms.bathrooms.length > 0 &&
rooms.bathrooms.every(
(b: any) =>
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 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];
}
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;
const kitchenOk = conditionReport.rooms.kitchen?.room_info?.overall_condition_of_the_room === "Good";
const bathroomsOk = Array.isArray(conditionReport.rooms.bathrooms) &&
conditionReport.rooms.bathrooms.length > 0 &&
conditionReport.rooms.bathrooms.every(
(b: any) => b?.room_info?.overall_condition_of_the_room === "Good"
);
const hasDefects = roomsWithDefects.length > 0;
return (
<div className="space-y-6 mt-8">
@ -295,7 +181,6 @@ export default function ConditionReport({
</CardHeader>
<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
@ -325,18 +210,18 @@ export default function ConditionReport({
<ChecklistItem
label={
heatingWorking
conditionReport.heating_system?.general_condition?.is_the_heating_system_in_working_order
? "Heating system operational"
: "Heating system not operational"
}
passed={heatingWorking}
alert={!heatingWorking}
passed={conditionReport.heating_system?.general_condition?.is_the_heating_system_in_working_order}
alert={!conditionReport.heating_system?.general_condition?.is_the_heating_system_in_working_order}
/>
<ChecklistItem
label={meetsSapThreshold ? "SAP rating meets minimum threshold (≥ 35)" : "SAP rating below recommended minimum"}
passed={meetsSapThreshold}
alert={!meetsSapThreshold}
label={sapOk ? "SAP rating meets minimum threshold (≥ 35)" : "SAP rating below recommended minimum"}
passed={sapOk}
alert={!sapOk}
note={`SAP Points: ${currentSapPoints}`}
/>
@ -344,16 +229,10 @@ export default function ConditionReport({
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"}`
}
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
@ -378,36 +257,27 @@ export default function ConditionReport({
/>
<ChecklistItem
label={
kitchenOk
? "Kitchen in good condition"
: "Kitchen not in good condition"
}
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"
}
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}
passed={enoughSpace}
alert={!enoughSpace}
note={`${occupantsToUse} occupants, ${numberOfBedrooms} bedrooms. ${areaPerPerson}m² per person`}
/>
</div>
</div>
</CardContent>
</Card>
</div>
);
}

View file

@ -143,9 +143,6 @@ export default async function PreAssessmentReport(
conditionReport = await getEnergyAssessmentFromS3(conditionReportMeta.s3JsonUri);
}
// console.log("conditionReport", conditionReport.rooms.utility)
console.log("Condition data", propertyMeta)
const nonIntrusiveSurvey = await getNonIntrusiveSurvey(propertyMeta.uprn);
const retrofitFeatures = formatRetrofitFeatures(conditionReportData);