diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/assessment/ConditionReport.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/assessment/ConditionReport.tsx
new file mode 100644
index 0000000..2186454
--- /dev/null
+++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/assessment/ConditionReport.tsx
@@ -0,0 +1,221 @@
+"use client";
+
+import {
+ Card,
+ CardContent,
+ CardHeader,
+} from "@/app/shadcn_components/ui/card";
+import {
+ CheckCircle,
+ XCircle,
+ HelpCircle,
+} from "lucide-react";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "@/app/shadcn_components/ui/accordion";
+import clsx from "clsx";
+
+function ChecklistItem({
+ label,
+ passed,
+ note,
+ alert = false,
+ roomsWithIssues = [],
+}: {
+ label: string;
+ passed?: boolean;
+ note?: string;
+ alert?: boolean;
+ roomsWithIssues?: [string, any][];
+}) {
+ const icon = passed === true ? (
+
+ ) : passed === false ? (
+
+ ) : (
+
+ );
+
+ return (
+
+
+ {icon}
+
+ {label}
+ {note && (
+
+ ({note})
+
+ )}
+
+
+
+ {roomsWithIssues.length > 0 && (
+
+ {roomsWithIssues.map(([roomName, room]: any) => (
+
+ {formatRoomName(roomName)}
+
+
+ {room.room_info?.overall_condition_of_the_room && (
+
+ Condition: {room.room_info.overall_condition_of_the_room}
+
+ )}
+ {room.room_info?.does_the_room_have_any_defects && (
+
+ Defects: {room.room_info.does_the_room_have_any_defects}
+
+ )}
+ {room.room_info?.ventilation_info
+ ?.are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room !==
+ undefined && (
+
+ Damp/Mould:{" "}
+ {room.room_info.ventilation_info
+ .are_there_any_visible_or_reported_signs_of_damp_mould_or_excessive_condensation_within_the_room
+ ? "Yes"
+ : "No"}
+
+ )}
+ {room.room_info?.windows_info?.condition_of_the_windows && (
+
+ Window Condition:{" "}
+ {room.room_info.windows_info.condition_of_the_windows}
+
+ )}
+
+
+
+ ))}
+
+ )}
+
+ );
+}
+
+function formatRoomName(name: string) {
+ return name
+ .replaceAll("_", " ")
+ .replace(/\b\w/g, (l) => l.toUpperCase())
+ .replace("Room Info", "Room");
+}
+
+export default function ConditionReport({
+ conditionReport,
+}: {
+ conditionReport: {
+ rooms: Record;
+ [key: string]: any;
+ }
+}) {
+ const rooms = conditionReport.rooms;
+
+ 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 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";
+ }
+ );
+
+ return (
+
+
+
+ Decent Homes Checklist
+
+
+ 0}
+ roomsWithIssues={roomsWithDamp}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/assessment/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/assessment/page.tsx
new file mode 100644
index 0000000..7368036
--- /dev/null
+++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/assessment/page.tsx
@@ -0,0 +1,199 @@
+import EpcCard from "@/app/components/building-passport/EpcCard";
+import FeatureTable from "@/app/components/building-passport/FeatureTable";
+import {
+ ConditionReportData,
+ PropertyDetailsEpc,
+ PropertyDetailsSpatial,
+ PropertyMeta,
+} from "@/app/db/schema/property";
+import { formatDateTime } from "@/app/utils";
+import {
+ generalColumns,
+ nonInstrusiveColumns,
+ retrofitColumns,
+} from "@/app/components/building-passport/FeatureTableColumns";
+import {
+ formatGeneralFeatures,
+ formatHeatDemandFeatures,
+ formatRetrofitFeatures,
+ getConditionReport,
+ getPropertyMeta,
+ getSpatialData,
+ getNonIntrusiveSurvey,
+ getDocument,
+ getEnergyAssessmentFromS3
+} from "../utils";
+import ConditionReport from "@/app/portfolio/[slug]/building-passport/[propertyId]/assessment/ConditionReport";
+
+interface PropertyDetailsCardProps {
+ conditionReportData: PropertyDetailsEpc;
+ propertyMeta: PropertyMeta;
+ propertyDetailsSpatial: PropertyDetailsSpatial;
+}
+
+const rowTitleStyle = "text-brandblue align-top pb-3";
+const rowValueStyle = "text-brandblue text-end pr-8 pt-1 align-top pb-3";
+
+function PropertyDetailsCard({
+ conditionReportData,
+ propertyMeta,
+ propertyDetailsSpatial,
+}: PropertyDetailsCardProps) {
+ const propertyText = [propertyMeta.builtForm, propertyMeta.propertyType]
+ .filter(Boolean)
+ .join(" ");
+
+ return (
+
+
+
+
+
+
+ | Year built: |
+ {propertyMeta.yearBuilt} |
+
+
+ | Property Type: |
+ {propertyText} |
+
+
+ | Total floor area: |
+
+ {`${conditionReportData.totalFloorArea} m`}
+ 2
+ |
+
+
+ | In conservation area: |
+
+ {propertyDetailsSpatial.conservationStatus ? "Yes" : "No"}
+ |
+
+
+ | Is listed: |
+
+ {propertyDetailsSpatial.isListedBuilding ? "Yes" : "No"}
+ |
+
+
+ | Is heritage: |
+
+ {propertyDetailsSpatial.isHeritageBuilding ? "Yes" : "No"}
+ |
+
+
+
+
+
+
+
+ | Local Authority: |
+ {propertyMeta.localAuthority} |
+
+
+ | Constituency: |
+ {propertyMeta.constituency} |
+
+
+ | Tenure |
+ {propertyMeta.tenure} |
+
+
+ | Number of Habitable Rooms: |
+
+ {propertyMeta.numberOfRooms || "unkown"}
+ |
+
+
+
+
+
+ );
+}
+
+const formatDate = (dateString: Date) => {
+ const date = new Date(dateString);
+ return date.toLocaleDateString("en-GB", {
+ weekday: "long", // "Monday" through "Sunday"
+ year: "numeric", // "2024"
+ month: "long", // "January" through "December"
+ day: "numeric", // "1", "2", ..., "31"
+ });
+};
+
+export default async function PreAssessmentReport(
+ props: {
+ params: Promise<{ slug: string; propertyId: string }>;
+ }
+) {
+ const params = await props.params;
+ const propertyMeta = await getPropertyMeta(params.propertyId);
+ const conditionReportData = await getConditionReport(params.propertyId);
+ const propertyDetailsSpatial = await getSpatialData(propertyMeta.uprn);
+ const generalFeatures = formatGeneralFeatures(
+ conditionReportData,
+ propertyMeta.propertyType
+ );
+ const conditionReportMeta = await getDocument(
+ { uprn: String(propertyMeta.uprn), documentType: "ECO_CONDITION_REPORT" }
+ );
+ let conditionReport = { rooms: {} };
+ if (conditionReportMeta && conditionReportMeta.s3JsonUri) {
+ conditionReport = await getEnergyAssessmentFromS3(conditionReportMeta.s3JsonUri);
+ }
+
+ console.log("conditionReport", conditionReport.rooms.kitchen)
+
+ const nonIntrusiveSurvey = await getNonIntrusiveSurvey(propertyMeta.uprn);
+
+ const retrofitFeatures = formatRetrofitFeatures(conditionReportData);
+
+ const heatingDemand = formatHeatDemandFeatures(conditionReportData);
+
+ return (
+
+
+ Last updated: {formatDateTime(propertyMeta.updatedAt)}
+
+
+
+ {
+ Object.keys(conditionReportMeta).length > 0 &&
+ }
+
+ {nonIntrusiveSurvey && (
+
+
Non-Intrusive Survey
+
+ Conducted by: {nonIntrusiveSurvey.surveyor} on{" "}
+ {formatDate(nonIntrusiveSurvey.surveyDate)}
+
+
+
+ )}
+
General Features
+
+
Existing Property Features
+
+
Heating Demand
+
+
+ );
+}
diff --git a/src/app/shadcn_components/ui/accordion.tsx b/src/app/shadcn_components/ui/accordion.tsx
new file mode 100644
index 0000000..7511978
--- /dev/null
+++ b/src/app/shadcn_components/ui/accordion.tsx
@@ -0,0 +1,58 @@
+"use client"
+
+import * as React from "react"
+import * as AccordionPrimitive from "@radix-ui/react-accordion"
+import { ChevronDown } from "lucide-react"
+
+import { cn } from '@/lib/utils'
+
+const Accordion = AccordionPrimitive.Root
+
+const AccordionItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AccordionItem.displayName = "AccordionItem"
+
+const AccordionTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ svg]:rotate-180",
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+))
+AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
+
+const AccordionContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+))
+
+AccordionContent.displayName = AccordionPrimitive.Content.displayName
+
+export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }