diff --git a/src/app/components/building-passport/EnergyEfficiencyImpactCard.tsx b/src/app/components/building-passport/EnergyEfficiencyImpactCard.tsx
new file mode 100644
index 0000000..1f9c207
--- /dev/null
+++ b/src/app/components/building-passport/EnergyEfficiencyImpactCard.tsx
@@ -0,0 +1,93 @@
+interface EnergyEfficiencyImpactCardProps {
+ currentEpcRating: string;
+ expectedEpcRating: string;
+ currentSapPoints: number;
+ expectedSapPoints: number;
+ totalSapPoints: number;
+}
+
+export function EnergyEfficiencyImpactCard({
+ currentEpcRating,
+ expectedEpcRating,
+ currentSapPoints,
+ expectedSapPoints,
+ totalSapPoints,
+}: EnergyEfficiencyImpactCardProps) {
+ return (
+
+
+
+
+ | Energy Efficiency Impact |
+
+
+
+ | Current EPC Rating: |
+
+ {currentSapPoints + " " + currentEpcRating}
+ |
+
+
+
+ | Expected EPC Rating: |
+
+ {Math.floor(expectedSapPoints) + " " + expectedEpcRating}
+ |
+
+
+
+ |
+ Total SAP Points Improvement:
+ |
+
+ {Math.round((totalSapPoints + Number.EPSILON) * 100) / 100}
+ |
+
+
+
+
+ );
+}
+
+interface SecondaryEnergyEfficiencyImpactCardProps {
+ TotalCo2Savings: number;
+ totalEnergyCostSavings: number;
+ totalHeatDemandSavings: number;
+}
+
+export function SecondaryEnergyEfficiencyImpactCard({
+ TotalCo2Savings,
+ totalEnergyCostSavings,
+ totalHeatDemandSavings,
+}: SecondaryEnergyEfficiencyImpactCardProps) {
+ return (
+
+
+
+
+ |
+
+
+
+ |
+ CO2 Reduction
+ |
+ {TotalCo2Savings.toFixed(1)}t |
+
+
+
+ | Energy Savings |
+
+ {totalHeatDemandSavings.toFixed(0) + "kWh"}
+ |
+
+
+
+ | Energy Bill Savings |
+ {"£" + Math.round(totalEnergyCostSavings)} |
+
+
+
+
+ );
+}
diff --git a/src/app/components/building-passport/RecommendationCard.tsx b/src/app/components/building-passport/RecommendationCard.tsx
index f882912..da9aeb9 100644
--- a/src/app/components/building-passport/RecommendationCard.tsx
+++ b/src/app/components/building-passport/RecommendationCard.tsx
@@ -35,6 +35,31 @@ const TitleMap = {
roof_insulation: "Roof Insulation",
};
+type RecommendationCardProps = {
+ componentType: RecommendationType;
+ recommendationData: Recommendation[];
+ setCostMap: Dispatch>;
+ costMap: RecommendationMetricMap;
+ setTotalEstimatedCost: Dispatch>;
+ sapMap: RecommendationMetricMap;
+ setSapMap: Dispatch>;
+ setTotalSapPoints: Dispatch>;
+ currentSapPoints: number;
+ setExpectedEpcRating: Dispatch>;
+ setTotalLabourDays: Dispatch>;
+ labourDaysMap: RecommendationMetricMap;
+ setLabourDaysMap: Dispatch>;
+ setCo2SavingsMap: Dispatch>;
+ co2SavingsMap: RecommendationMetricMap;
+ setTotalCo2Savings: Dispatch>;
+ setEnergyCostSavingsMap: Dispatch>;
+ energyCostSavingsMap: RecommendationMetricMap;
+ setTotalEnergyCostSavings: Dispatch>;
+ setHeatDemandMap: Dispatch>;
+ heatDemandMap: RecommendationMetricMap;
+ setTotalHeatDemandSavings: Dispatch>;
+};
+
export default function RecommendationCard({
componentType,
recommendationData,
@@ -46,18 +71,19 @@ export default function RecommendationCard({
setTotalSapPoints,
currentSapPoints,
setExpectedEpcRating,
-}: {
- componentType: RecommendationType;
- recommendationData: Recommendation[];
- setCostMap: Dispatch>;
- costMap: RecommendationMetricMap;
- setTotalEstimatedCost: Dispatch>;
- sapMap: RecommendationMetricMap;
- setSapMap: Dispatch>;
- setTotalSapPoints: Dispatch>;
- currentSapPoints: number;
- setExpectedEpcRating: Dispatch>;
-}) {
+ setTotalLabourDays,
+ labourDaysMap,
+ setLabourDaysMap,
+ setCo2SavingsMap,
+ co2SavingsMap,
+ setTotalCo2Savings,
+ setEnergyCostSavingsMap,
+ energyCostSavingsMap,
+ setTotalEnergyCostSavings,
+ setHeatDemandMap,
+ heatDemandMap,
+ setTotalHeatDemandSavings,
+}: RecommendationCardProps) {
const defaultComponent = recommendationData.find(
(rec: Recommendation) => rec.default
) as Recommendation;
@@ -138,6 +164,22 @@ export default function RecommendationCard({
setTotalSapPoints={setTotalSapPoints}
currentSapPoints={currentSapPoints}
setExpectedEpcRating={setExpectedEpcRating}
+ // Labour
+ setTotalLabourDays={setTotalLabourDays}
+ labourDaysMap={labourDaysMap}
+ setLabourDaysMap={setLabourDaysMap}
+ // Co2
+ setCo2SavingsMap={setCo2SavingsMap}
+ co2SavingsMap={co2SavingsMap}
+ setTotalCo2Savings={setTotalCo2Savings}
+ // Energy Cost
+ setEnergyCostSavingsMap={setEnergyCostSavingsMap}
+ energyCostSavingsMap={energyCostSavingsMap}
+ setTotalEnergyCostSavings={setTotalEnergyCostSavings}
+ // Heat Demand
+ setHeatDemandMap={setHeatDemandMap}
+ heatDemandMap={heatDemandMap}
+ setTotalHeatDemandSavings={setTotalHeatDemandSavings}
/>
);
diff --git a/src/app/components/building-passport/RecommendationContainer.tsx b/src/app/components/building-passport/RecommendationContainer.tsx
index 8d03d8a..db26845 100644
--- a/src/app/components/building-passport/RecommendationContainer.tsx
+++ b/src/app/components/building-passport/RecommendationContainer.tsx
@@ -5,14 +5,17 @@ import {
RecommendationType,
} from "@/app/db/schema/recommendations";
import RecommendationCard from "./RecommendationCard";
-import RecommendationCostSummaryCard from "./RecommendationCostSummaryCard";
+import WorksPackageCard from "./WorksPackageCard";
import { Separator } from "@/app/shadcn_components/ui/separator";
import { PropertyMeta } from "@/app/db/schema/property";
import { sapToEpc } from "@/app/utils";
import { useState } from "react";
import { sumRecommendationMetricMap } from "@/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils";
import { RecommendationMetricMap } from "@/types/recommendations";
-import RecommendationEpcSummaryCard from "./RecommendationEpcSummaryCard";
+import {
+ EnergyEfficiencyImpactCard,
+ SecondaryEnergyEfficiencyImpactCard,
+} from "./EnergyEfficiencyImpactCard";
interface RecommendationContainerProps {
recommendations: Recommendation[];
@@ -32,6 +35,15 @@ const typeToCategoryMap: { [key in RecommendationType]?: RecommendationType } =
exposed_floor_insulation: "floor_insulation",
};
+const emptyImpactState = {
+ estimatedCost: 0,
+ sapPoints: 0,
+ labourDays: 0,
+ co2EquivalentSavings: 0,
+ energyCostSavings: 0,
+ heatDemand: 0,
+};
+
export default function RecommendationContainer({
recommendations,
propertyMeta,
@@ -48,37 +60,35 @@ export default function RecommendationContainer({
return acc;
}, {} as Record);
- console.log(categorizedRecommendations);
-
const defaultWallsRecommendations =
categorizedRecommendations.wall_insulation?.find(
(rec: Recommendation) => rec.default
- ) || { estimatedCost: 0, sapPoints: 0 };
+ ) || emptyImpactState;
const defaultFloorRecommendations =
categorizedRecommendations.floor_insulation?.find(
(rec: Recommendation) => rec.default
- ) || { estimatedCost: 0, sapPoints: 0 };
+ ) || emptyImpactState;
const defaultRoofRecommendations =
categorizedRecommendations.roof_insulation?.find(
(rec: Recommendation) => rec.default
- ) || { estimatedCost: 0, sapPoints: 0 };
+ ) || emptyImpactState;
const defaultVentiliationRecommendations =
categorizedRecommendations.mechanical_ventilation?.find(
(rec: Recommendation) => rec.default
- ) || { estimatedCost: 0, sapPoints: 0 };
+ ) || emptyImpactState;
const defaultFireplaceRecommendations =
categorizedRecommendations.sealing_open_fireplace?.find(
(rec: Recommendation) => rec.default
- ) || { estimatedCost: 0, sapPoints: 0 };
+ ) || emptyImpactState;
const defaultLightingRecommendations =
categorizedRecommendations.low_energy_lighting?.find(
(rec: Recommendation) => rec.default
- ) || { estimatedCost: 0, sapPoints: 0 };
+ ) || emptyImpactState;
const [costMap, setCostMap] = useState({
wall_insulation: defaultWallsRecommendations?.estimatedCost || 0,
@@ -99,6 +109,49 @@ export default function RecommendationContainer({
low_energy_lighting: defaultLightingRecommendations.sapPoints || 0,
});
+ const [labourDaysMap, setLabourDaysMap] = useState({
+ wall_insulation: defaultWallsRecommendations?.labourDays || 0,
+ floor_insulation: defaultFloorRecommendations.labourDays || 0,
+ roof_insulation: defaultRoofRecommendations.labourDays || 0,
+ mechanical_ventilation: defaultVentiliationRecommendations.labourDays || 0,
+ sealing_open_fireplace: defaultFireplaceRecommendations.labourDays || 0,
+ low_energy_lighting: defaultLightingRecommendations.labourDays || 0,
+ });
+
+ const [co2SavingsMap, setCo2SavingsMap] = useState({
+ wall_insulation: defaultWallsRecommendations?.co2EquivalentSavings || 0,
+ floor_insulation: defaultFloorRecommendations.co2EquivalentSavings || 0,
+ roof_insulation: defaultRoofRecommendations.co2EquivalentSavings || 0,
+ mechanical_ventilation:
+ defaultVentiliationRecommendations.co2EquivalentSavings || 0,
+ sealing_open_fireplace:
+ defaultFireplaceRecommendations.co2EquivalentSavings || 0,
+ low_energy_lighting:
+ defaultLightingRecommendations.co2EquivalentSavings || 0,
+ });
+
+ const [energyCostSavingsMap, setEnergyCostSavingsMap] =
+ useState({
+ wall_insulation: defaultWallsRecommendations?.energyCostSavings || 0,
+ floor_insulation: defaultFloorRecommendations.energyCostSavings || 0,
+ roof_insulation: defaultRoofRecommendations.energyCostSavings || 0,
+ mechanical_ventilation:
+ defaultVentiliationRecommendations.energyCostSavings || 0,
+ sealing_open_fireplace:
+ defaultFireplaceRecommendations.energyCostSavings || 0,
+ low_energy_lighting:
+ defaultLightingRecommendations.energyCostSavings || 0,
+ });
+
+ const [heatDemandMap, setHeatDemandMap] = useState({
+ wall_insulation: defaultWallsRecommendations?.heatDemand || 0,
+ floor_insulation: defaultFloorRecommendations.heatDemand || 0,
+ roof_insulation: defaultRoofRecommendations.heatDemand || 0,
+ mechanical_ventilation: defaultVentiliationRecommendations.heatDemand || 0,
+ sealing_open_fireplace: defaultFireplaceRecommendations.heatDemand || 0,
+ low_energy_lighting: defaultLightingRecommendations.heatDemand || 0,
+ });
+
const [totalEstimatedCost, setTotalEstimatedCost] = useState(
sumRecommendationMetricMap(costMap)
);
@@ -107,10 +160,25 @@ export default function RecommendationContainer({
sumRecommendationMetricMap(sapMap)
);
+ const [totalLabourDays, setTotalLabourDays] = useState(
+ sumRecommendationMetricMap(labourDaysMap)
+ );
+
+ const [totalCo2Savings, setTotalCo2Savings] = useState(
+ sumRecommendationMetricMap(co2SavingsMap)
+ );
+
+ const [totalEnergyCostSavings, setTotalEnergyCostSavings] = useState(
+ sumRecommendationMetricMap(energyCostSavingsMap)
+ );
+
+ const [totalHeatDemandSavings, setTotalHeatDemandSavings] = useState(
+ sumRecommendationMetricMap(heatDemandMap)
+ );
+
const currentEpcRating = propertyMeta.currentEpcRating;
const currentSapPoints = propertyMeta.currentSapPoints;
- //TODO: Use Math.min while we have dummy SAP points
const expectedSapPoints = Math.min(currentSapPoints + totalSapPoints, 100);
const [expectedEpcRating, setExpectedEpcRating] = useState(
sapToEpc(expectedSapPoints)
@@ -119,14 +187,23 @@ export default function RecommendationContainer({
return (
<>
-
+
+
-
@@ -141,14 +218,32 @@ export default function RecommendationContainer({
// entires means we loose the typing on the key
componentType={componentType as RecommendationType}
recommendationData={recommendationData}
+ // cost
setCostMap={setCostMap}
costMap={costMap}
setTotalEstimatedCost={setTotalEstimatedCost}
+ // Sap
setSapMap={setSapMap}
sapMap={sapMap}
setTotalSapPoints={setTotalSapPoints}
currentSapPoints={currentSapPoints}
setExpectedEpcRating={setExpectedEpcRating}
+ // Labour
+ setTotalLabourDays={setTotalLabourDays}
+ labourDaysMap={labourDaysMap}
+ setLabourDaysMap={setLabourDaysMap}
+ // Co2
+ setCo2SavingsMap={setCo2SavingsMap}
+ co2SavingsMap={co2SavingsMap}
+ setTotalCo2Savings={setTotalCo2Savings}
+ // Energy Cost
+ setEnergyCostSavingsMap={setEnergyCostSavingsMap}
+ energyCostSavingsMap={energyCostSavingsMap}
+ setTotalEnergyCostSavings={setTotalEnergyCostSavings}
+ // Heat Demand
+ setHeatDemandMap={setHeatDemandMap}
+ heatDemandMap={heatDemandMap}
+ setTotalHeatDemandSavings={setTotalHeatDemandSavings}
/>
);
}
diff --git a/src/app/components/building-passport/RecommendationCostSummaryCard.tsx b/src/app/components/building-passport/RecommendationCostSummaryCard.tsx
deleted file mode 100644
index ccf5125..0000000
--- a/src/app/components/building-passport/RecommendationCostSummaryCard.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-"use client";
-import { formatNumber } from "@/app/utils";
-
-export default function RecommendationCostSummaryCard({
- totalEstimatedCost,
- totalSapPoints,
-}: {
- totalEstimatedCost: number;
- totalSapPoints: number;
-}) {
- return (
-
-
-
- | Total Cost: |
- {"£" + formatNumber(totalEstimatedCost)} |
-
-
-
- |
- Total SAP Points Improvement:
- |
-
- {Math.round((totalSapPoints + Number.EPSILON) * 100) / 100}
- |
-
-
-
- );
-}
diff --git a/src/app/components/building-passport/RecommendationEpcSummaryCard.tsx b/src/app/components/building-passport/RecommendationEpcSummaryCard.tsx
deleted file mode 100644
index dcd1ebf..0000000
--- a/src/app/components/building-passport/RecommendationEpcSummaryCard.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-interface RecommendationEpcSummaryCardProps {
- currentEpcRating: string;
- expectedEpcRating: string;
-}
-
-export default function RecommendationEpcSummaryCard({
- currentEpcRating,
- expectedEpcRating,
-}: RecommendationEpcSummaryCardProps) {
- return (
-
-
-
- | Current EPC Rating: |
- {currentEpcRating} |
-
-
-
- | Expected EPC Rating: |
- {expectedEpcRating} |
-
-
-
- );
-}
diff --git a/src/app/components/building-passport/RecommendationModal.tsx b/src/app/components/building-passport/RecommendationModal.tsx
index 49f2541..9ff7356 100644
--- a/src/app/components/building-passport/RecommendationModal.tsx
+++ b/src/app/components/building-passport/RecommendationModal.tsx
@@ -22,6 +22,18 @@ interface RecommendationModalProps {
setTotalSapPoints: Dispatch>;
currentSapPoints: number;
setExpectedEpcRating: Dispatch>;
+ setTotalLabourDays: Dispatch>;
+ labourDaysMap: RecommendationMetricMap;
+ setLabourDaysMap: Dispatch>;
+ setCo2SavingsMap: Dispatch>;
+ co2SavingsMap: RecommendationMetricMap;
+ setTotalCo2Savings: Dispatch>;
+ setEnergyCostSavingsMap: Dispatch>;
+ energyCostSavingsMap: RecommendationMetricMap;
+ setTotalEnergyCostSavings: Dispatch>;
+ setHeatDemandMap: Dispatch>;
+ heatDemandMap: RecommendationMetricMap;
+ setTotalHeatDemandSavings: Dispatch>;
}
export default function RecommendationModal({
@@ -38,6 +50,18 @@ export default function RecommendationModal({
setTotalSapPoints,
currentSapPoints,
setExpectedEpcRating,
+ setTotalLabourDays,
+ labourDaysMap,
+ setLabourDaysMap,
+ setCo2SavingsMap,
+ co2SavingsMap,
+ setTotalCo2Savings,
+ setEnergyCostSavingsMap,
+ energyCostSavingsMap,
+ setTotalEnergyCostSavings,
+ setHeatDemandMap,
+ heatDemandMap,
+ setTotalHeatDemandSavings,
}: RecommendationModalProps) {
const [saveButtonDisabled, setSaveButtonDisabled] = useState(true);
@@ -77,8 +101,6 @@ export default function RecommendationModal({
// update the cost sum
setTotalEstimatedCost(sumRecommendationMetricMap(newCostMap));
- console.log("B4", sapMap);
-
// Update the sap map
const newSapMap = {
...sapMap,
@@ -86,11 +108,8 @@ export default function RecommendationModal({
};
setSapMap(newSapMap);
- console.log("AFTER", newSapMap);
-
// update the sap sum
const newSapImprovement = sumRecommendationMetricMap(newSapMap);
- console.log("newSapImprovement", newSapImprovement);
setTotalSapPoints(newSapImprovement);
// TODO: While we have placeholder SAP points, constrain to 100
@@ -98,6 +117,52 @@ export default function RecommendationModal({
// update the expected EPC rating
setExpectedEpcRating(sapToEpc(newSapPoints));
+
+ // Update the labour days map
+ const newLabourDaysMap = {
+ ...labourDaysMap,
+ [title]: recommendationData[newIndex]?.labourDays || 0,
+ };
+
+ setLabourDaysMap(newLabourDaysMap);
+
+ // update the labour days sum
+ setTotalLabourDays(sumRecommendationMetricMap(newLabourDaysMap));
+
+ // Update the co2 savings map
+ const newCo2SavingsMap = {
+ ...co2SavingsMap,
+ [title]: recommendationData[newIndex]?.co2EquivalentSavings || 0,
+ };
+
+ setCo2SavingsMap(newCo2SavingsMap);
+
+ // update the co2 savings sum
+ setTotalCo2Savings(sumRecommendationMetricMap(newCo2SavingsMap));
+
+ // Update the energy cost savings map
+ const newEnergyCostSavingsMap = {
+ ...energyCostSavingsMap,
+ [title]: recommendationData[newIndex]?.energyCostSavings || 0,
+ };
+
+ setEnergyCostSavingsMap(newEnergyCostSavingsMap);
+
+ // update the energy cost savings sum
+ setTotalEnergyCostSavings(
+ sumRecommendationMetricMap(newEnergyCostSavingsMap)
+ );
+
+ // Update the heat demand savings map
+ const newHeatDemandMap = {
+ ...heatDemandMap,
+ [title]: recommendationData[newIndex]?.heatDemand || 0,
+ };
+
+ setHeatDemandMap(newHeatDemandMap);
+
+ // update the heat demand savings sum
+ setTotalHeatDemandSavings(sumRecommendationMetricMap(newHeatDemandMap));
}
return (
diff --git a/src/app/components/building-passport/WorksPackageCard.tsx b/src/app/components/building-passport/WorksPackageCard.tsx
new file mode 100644
index 0000000..a07fd40
--- /dev/null
+++ b/src/app/components/building-passport/WorksPackageCard.tsx
@@ -0,0 +1,35 @@
+"use client";
+import { convertDaysToWorkingWeeks, formatNumber } from "@/app/utils";
+
+export default function WorksPackageCard({
+ totalEstimatedCost,
+ totalLabourDays,
+}: {
+ totalEstimatedCost: number;
+ totalLabourDays: number;
+}) {
+ return (
+
+
+
+ | Works Package |
+
+
+
+ | Total Cost: |
+ {"£" + formatNumber(totalEstimatedCost)} |
+
+
+
+ | Trades required: |
+ {"1-2"} |
+
+
+
+ | Estimated Duration: |
+ {convertDaysToWorkingWeeks(totalLabourDays)} |
+
+
+
+ );
+}
diff --git a/src/app/db/migrations/0054_sharp_mojo.sql b/src/app/db/migrations/0054_sharp_mojo.sql
new file mode 100644
index 0000000..16192c9
--- /dev/null
+++ b/src/app/db/migrations/0054_sharp_mojo.sql
@@ -0,0 +1,11 @@
+CREATE TABLE IF NOT EXISTS "property_details_spatial" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "uprn" bigint,
+ "x_coordinate" real,
+ "y_coordinate" real,
+ "latitude" real,
+ "longitude" real,
+ "conservation_status" boolean,
+ "is_listed_building" boolean,
+ "is_heritage_building" boolean
+);
diff --git a/src/app/db/migrations/meta/0054_snapshot.json b/src/app/db/migrations/meta/0054_snapshot.json
new file mode 100644
index 0000000..cd1fb13
--- /dev/null
+++ b/src/app/db/migrations/meta/0054_snapshot.json
@@ -0,0 +1,1442 @@
+{
+ "version": "5",
+ "dialect": "pg",
+ "id": "b92e97d1-c6e9-4749-b205-ad3cf5470a4d",
+ "prevId": "5bab96d7-f042-492e-a08f-a2ad4e7a2f69",
+ "tables": {
+ "material": {
+ "name": "material",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "type",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "depth": {
+ "name": "depth",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "depth_unit": {
+ "name": "depth_unit",
+ "type": "depth_unit",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_unit": {
+ "name": "cost_unit",
+ "type": "cost_unit",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "r_value_per_mm": {
+ "name": "r_value_per_mm",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "r_value_unit": {
+ "name": "r_value_unit",
+ "type": "r_value_unit",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thermal_conductivity": {
+ "name": "thermal_conductivity",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thermal_conductivity_unit": {
+ "name": "thermal_conductivity_unit",
+ "type": "thermal_conductivity_unit",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "link": {
+ "name": "link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "prime_material_cost": {
+ "name": "prime_material_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "material_cost": {
+ "name": "material_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labour_cost": {
+ "name": "labour_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labour_hours_per_unit": {
+ "name": "labour_hours_per_unit",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plant_cost": {
+ "name": "plant_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_cost": {
+ "name": "total_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost": {
+ "name": "cost",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {}
+ },
+ "portfolio": {
+ "name": "portfolio",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "budget": {
+ "name": "budget",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cost": {
+ "name": "cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_properties": {
+ "name": "number_of_properties",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_equivalent_savings": {
+ "name": "co2_equivalent_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_savings": {
+ "name": "energy_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_cost_savings": {
+ "name": "energy_cost_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "property_valuation_increase": {
+ "name": "property_valuation_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rental_yield_increase": {
+ "name": "rental_yield_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_work_hours": {
+ "name": "total_work_hours",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labour_days": {
+ "name": "labour_days",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {}
+ },
+ "portfolioUsers": {
+ "name": "portfolioUsers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "portfolioUsers_user_id_user_id_fk": {
+ "name": "portfolioUsers_user_id_user_id_fk",
+ "tableFrom": "portfolioUsers",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "portfolioUsers_portfolio_id_portfolio_id_fk": {
+ "name": "portfolioUsers_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolioUsers",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "property": {
+ "name": "property",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "creation_status": {
+ "name": "creation_status",
+ "type": "creation_status",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "building_reference_number": {
+ "name": "building_reference_number",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_pre_condition_report": {
+ "name": "has_pre_condition_report",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_recommendations": {
+ "name": "has_recommendations",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "property_type": {
+ "name": "property_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "built_form": {
+ "name": "built_form",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "local_authority": {
+ "name": "local_authority",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "constituency": {
+ "name": "constituency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_rooms": {
+ "name": "number_of_rooms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "year_built": {
+ "name": "year_built",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tenure": {
+ "name": "tenure",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "current_epc_rating": {
+ "name": "current_epc_rating",
+ "type": "epc",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "current_sap_points": {
+ "name": "current_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "property_portfolio_id_portfolio_id_fk": {
+ "name": "property_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "property_details_epc": {
+ "name": "property_details_epc",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "full_address": {
+ "name": "full_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_floor_area": {
+ "name": "total_floor_area",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "walls": {
+ "name": "walls",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "walls_rating": {
+ "name": "walls_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof": {
+ "name": "roof",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_rating": {
+ "name": "roof_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor": {
+ "name": "floor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_rating": {
+ "name": "floor_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "windows": {
+ "name": "windows",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "windows_rating": {
+ "name": "windows_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating": {
+ "name": "heating",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_rating": {
+ "name": "heating_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_controls": {
+ "name": "heating_controls",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_controls_rating": {
+ "name": "heating_controls_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hot_water": {
+ "name": "hot_water",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hot_water_rating": {
+ "name": "hot_water_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lighting": {
+ "name": "lighting",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lighting_rating": {
+ "name": "lighting_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mainfuel": {
+ "name": "mainfuel",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation": {
+ "name": "ventilation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "solar_pv": {
+ "name": "solar_pv",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "solar_hot_water": {
+ "name": "solar_hot_water",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wind_turbine": {
+ "name": "wind_turbine",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_height": {
+ "name": "floor_height",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_heated_rooms": {
+ "name": "number_heated_rooms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_loss_corridor": {
+ "name": "heat_loss_corridor",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "unheated_corridor_length": {
+ "name": "unheated_corridor_length",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_open_fireplaces": {
+ "name": "number_of_open_fireplaces",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_extensions": {
+ "name": "number_of_extensions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_storeys": {
+ "name": "number_of_storeys",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mains_gas": {
+ "name": "mains_gas",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_tariff": {
+ "name": "energy_tariff",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "primary_energy_consumption": {
+ "name": "primary_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_emissions": {
+ "name": "co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "adjusted_energy_consumption": {
+ "name": "adjusted_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "property_details_epc_property_id_property_id_fk": {
+ "name": "property_details_epc_property_id_property_id_fk",
+ "tableFrom": "property_details_epc",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "property_details_epc_portfolio_id_portfolio_id_fk": {
+ "name": "property_details_epc_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_details_epc",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "property_details_meter": {
+ "name": "property_details_meter",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_supplier": {
+ "name": "energy_supplier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gas_supplier": {
+ "name": "gas_supplier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "meter_reading_total": {
+ "name": "meter_reading_total",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "meter_reading_electricity": {
+ "name": "meter_reading_electricity",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "meter_reading_gas": {
+ "name": "meter_reading_gas",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {}
+ },
+ "property_details_spatial": {
+ "name": "property_details_spatial",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "x_coordinate": {
+ "name": "x_coordinate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "y_coordinate": {
+ "name": "y_coordinate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "latitude": {
+ "name": "latitude",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "longitude": {
+ "name": "longitude",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservation_status": {
+ "name": "conservation_status",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed_building": {
+ "name": "is_listed_building",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_heritage_building": {
+ "name": "is_heritage_building",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {}
+ },
+ "property_targets": {
+ "name": "property_targets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "epc": {
+ "name": "epc",
+ "type": "epc",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_demand": {
+ "name": "heat_demand",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "property_targets_property_id_property_id_fk": {
+ "name": "property_targets_property_id_property_id_fk",
+ "tableFrom": "property_targets",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "property_targets_portfolio_id_portfolio_id_fk": {
+ "name": "property_targets_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_targets",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "plan": {
+ "name": "plan",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "plan_portfolio_id_portfolio_id_fk": {
+ "name": "plan_portfolio_id_portfolio_id_fk",
+ "tableFrom": "plan",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "plan_property_id_property_id_fk": {
+ "name": "plan_property_id_property_id_fk",
+ "tableFrom": "plan",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "plan_recommendations": {
+ "name": "plan_recommendations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "recommendation_id": {
+ "name": "recommendation_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "plan_recommendations_plan_id_plan_id_fk": {
+ "name": "plan_recommendations_plan_id_plan_id_fk",
+ "tableFrom": "plan_recommendations",
+ "tableTo": "plan",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "plan_recommendations_recommendation_id_recommendation_id_fk": {
+ "name": "plan_recommendations_recommendation_id_recommendation_id_fk",
+ "tableFrom": "plan_recommendations",
+ "tableTo": "recommendation",
+ "columnsFrom": [
+ "recommendation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "recommendation": {
+ "name": "recommendation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default": {
+ "name": "default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "starting_u_value": {
+ "name": "starting_u_value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "new_u_value": {
+ "name": "new_u_value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_points": {
+ "name": "sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_demand": {
+ "name": "heat_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_equivalent_savings": {
+ "name": "co2_equivalent_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_savings": {
+ "name": "energy_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_cost_savings": {
+ "name": "energy_cost_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "property_valuation_increase": {
+ "name": "property_valuation_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rental_yield_increase": {
+ "name": "rental_yield_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_work_hours": {
+ "name": "total_work_hours",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "labour_days": {
+ "name": "labour_days",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recommendation_property_id_property_id_fk": {
+ "name": "recommendation_property_id_property_id_fk",
+ "tableFrom": "recommendation",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "recommendation_materials": {
+ "name": "recommendation_materials",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "recommendation_id": {
+ "name": "recommendation_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "material_id": {
+ "name": "material_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "depth": {
+ "name": "depth",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quantity": {
+ "name": "quantity",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "quantity_unit": {
+ "name": "quantity_unit",
+ "type": "unit_quantity",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recommendation_materials_recommendation_id_recommendation_id_fk": {
+ "name": "recommendation_materials_recommendation_id_recommendation_id_fk",
+ "tableFrom": "recommendation_materials",
+ "tableTo": "recommendation",
+ "columnsFrom": [
+ "recommendation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "recommendation_materials_material_id_material_id_fk": {
+ "name": "recommendation_materials_material_id_material_id_fk",
+ "tableFrom": "recommendation_materials",
+ "tableTo": "material",
+ "columnsFrom": [
+ "material_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {}
+ },
+ "user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "firstName": {
+ "name": "firstName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oauth_id": {
+ "name": "oauth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_provider": {
+ "name": "oauth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {}
+ }
+ },
+ "enums": {
+ "cost_unit": {
+ "name": "cost_unit",
+ "values": {
+ "gbp_sq_meter": "gbp_sq_meter",
+ "gbp_per_unit": "gbp_per_unit",
+ "gbp_per_m2": "gbp_per_m2"
+ }
+ },
+ "depth_unit": {
+ "name": "depth_unit",
+ "values": {
+ "mm": "mm"
+ }
+ },
+ "type": {
+ "name": "type",
+ "values": {
+ "suspended_floor_insulation": "suspended_floor_insulation",
+ "solid_floor_insulation": "solid_floor_insulation",
+ "external_wall_insulation": "external_wall_insulation",
+ "internal_wall_insulation": "internal_wall_insulation",
+ "cavity_wall_insulation": "cavity_wall_insulation",
+ "mechanical_ventilation": "mechanical_ventilation",
+ "loft_insulation": "loft_insulation",
+ "exposed_floor_insulation": "exposed_floor_insulation",
+ "flat_roof_insulation": "flat_roof_insulation",
+ "room_roof_insulation": "room_roof_insulation",
+ "iwi_wall_demolition": "iwi_wall_demolition",
+ "iwi_vapour_barrier": "iwi_vapour_barrier",
+ "iwi_redecoration": "iwi_redecoration",
+ "suspended_floor_demolition": "suspended_floor_demolition",
+ "suspended_floor_redecoration": "suspended_floor_redecoration",
+ "suspended_floor_vapour_barrier": "suspended_floor_vapour_barrier",
+ "solid_floor_demolition": "solid_floor_demolition",
+ "solid_floor_preparation": "solid_floor_preparation",
+ "solid_floor_vapour_barrier": "solid_floor_vapour_barrier",
+ "solid_floor_redecoration": "solid_floor_redecoration",
+ "ewi_wall_demolition": "ewi_wall_demolition",
+ "ewi_wall_preparation": "ewi_wall_preparation",
+ "ewi_wall_redecoration": "ewi_wall_redecoration",
+ "low_energy_lighting_installation": "low_energy_lighting_installation"
+ }
+ },
+ "r_value_unit": {
+ "name": "r_value_unit",
+ "values": {
+ "square_meter_kelvin_per_watt": "square_meter_kelvin_per_watt"
+ }
+ },
+ "thermal_conductivity_unit": {
+ "name": "thermal_conductivity_unit",
+ "values": {
+ "watt_per_meter_kelvin": "watt_per_meter_kelvin"
+ }
+ },
+ "goal": {
+ "name": "goal",
+ "values": {
+ "Valuation Improvement": "Valuation Improvement",
+ "Increasing EPC": "Increasing EPC",
+ "Reducing CO2 emissions": "Reducing CO2 emissions",
+ "Energy Savings": "Energy Savings",
+ "None": "None"
+ }
+ },
+ "role": {
+ "name": "role",
+ "values": {
+ "creator": "creator",
+ "admin": "admin",
+ "read": "read",
+ "write": "write"
+ }
+ },
+ "status": {
+ "name": "status",
+ "values": {
+ "scoping": "scoping",
+ "assessment": "assessment",
+ "tendering": "tendering",
+ "project underway": "project underway",
+ "completion; status: on track": "completion; status: on track",
+ "completion; status: delayed": "completion; status: delayed",
+ "completion; status: at risk": "completion; status: at risk",
+ "completion; status: completed": "completion; status: completed",
+ "needs review": "needs review"
+ }
+ },
+ "epc": {
+ "name": "epc",
+ "values": {
+ "A": "A",
+ "B": "B",
+ "C": "C",
+ "D": "D",
+ "E": "E",
+ "F": "F",
+ "G": "G"
+ }
+ },
+ "creation_status": {
+ "name": "creation_status",
+ "values": {
+ "LOADING": "LOADING",
+ "READY": "READY",
+ "ERROR": "ERROR"
+ }
+ },
+ "unit_quantity": {
+ "name": "unit_quantity",
+ "values": {
+ "m2": "m2",
+ "part": "part"
+ }
+ }
+ },
+ "schemas": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
+}
\ No newline at end of file
diff --git a/src/app/portfolio/[slug]/(portfolio)/page.tsx b/src/app/portfolio/[slug]/(portfolio)/page.tsx
index 483b864..11ed240 100644
--- a/src/app/portfolio/[slug]/(portfolio)/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/page.tsx
@@ -3,7 +3,7 @@ import { getPortfolio, getProperties } from "../utils";
import DataTable from "@/app/portfolio/[slug]/components/propertyTable";
import { columns } from "@/app/portfolio/[slug]/components/propertyTableColumns";
import { PropertyWithRelations } from "@/app/db/schema/property";
-import { formatNumber } from "@/app/utils";
+import { formatNumber, convertDaysToWorkingWeeks } from "@/app/utils";
// We enfore caching of data for 60 seconds
export const revalidate = 60;
@@ -87,35 +87,6 @@ function SummaryBox({
}
}
- function convertDaysToWorkingWeeks(days: number | null) {
- if (days === null) {
- return "-";
- }
-
- const workingDaysPerWeek = 5;
-
- // Convert days to working weeks
- const workingWeeks = days / workingDaysPerWeek;
-
- // Determine the range
- let lowerBound = Math.floor(workingWeeks);
- let upperBound = Math.ceil(workingWeeks);
-
- // Adjust if the fraction is very small, you might not count it as a full extra week
- if (workingWeeks - lowerBound < 0.2) {
- upperBound = lowerBound;
- }
-
- if (lowerBound === 1 && upperBound === 1) {
- return "1-2 weeks";
- }
-
- // Format the output
- return lowerBound === upperBound
- ? `${lowerBound} weeks`
- : `${lowerBound}-${upperBound} weeks`;
- }
-
const budgetFormatted = formatBudget(budget);
const totalCostFormatted = formatMoney(totalCost);
const totalValueIncreaseFormatted = formatMoney(propertyValuationIncrease);
diff --git a/src/app/utils.ts b/src/app/utils.ts
index ed5347b..a8b1ccf 100644
--- a/src/app/utils.ts
+++ b/src/app/utils.ts
@@ -1,5 +1,38 @@
import { Rating } from "./db/schema/property";
+export function convertDaysToWorkingWeeks(days: number | null) {
+ if (days === null) {
+ return "-";
+ }
+
+ const workingDaysPerWeek = 5;
+
+ // Convert days to working weeks
+ const workingWeeks = days / workingDaysPerWeek;
+
+ // Determine the range
+ let lowerBound = Math.floor(workingWeeks);
+ let upperBound = Math.ceil(workingWeeks);
+
+ // Adjust if the fraction is very small, you might not count it as a full extra week
+ if (workingWeeks - lowerBound < 0.2) {
+ upperBound = lowerBound;
+ }
+
+ if (lowerBound === 0 && upperBound === 1) {
+ return "1 week";
+ }
+
+ if (lowerBound === 1 && upperBound === 1) {
+ return "1-2 weeks";
+ }
+
+ // Format the output
+ return lowerBound === upperBound
+ ? `${lowerBound} weeks`
+ : `${lowerBound}-${upperBound} weeks`;
+}
+
export const getEpcColorClass = (letter: string) => {
switch (letter.toUpperCase()) {
case "A":