diff --git a/src/app/components/building-passport/EpcCard.tsx b/src/app/components/building-passport/EpcCard.tsx index ef0f0aa..4925a0b 100644 --- a/src/app/components/building-passport/EpcCard.tsx +++ b/src/app/components/building-passport/EpcCard.tsx @@ -1,18 +1,35 @@ export default function EpcCard({ epcRating, fullMargin = true, + expected = false, }: { epcRating: string; fullMargin: boolean; + expected?: boolean; }) { let marginClass = ""; if (fullMargin) { marginClass = "mx-auto"; } + let title; + let bgStyling; + if (expected) { + title = "Expected Energy Rating"; + bgStyling = "bg-green-600"; + } else { + title = "Energy Rating"; + bgStyling = "bg-brandblue"; + } + return ( -
-
Energy Rating
+
+
{title}
{epcRating}
); diff --git a/src/app/components/building-passport/GoToPlanButton.tsx b/src/app/components/building-passport/GoToPlanButton.tsx new file mode 100644 index 0000000..ef9d921 --- /dev/null +++ b/src/app/components/building-passport/GoToPlanButton.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { Button } from "@/app/shadcn_components/ui/button"; +import { ChevronRight } from "lucide-react"; +import { usePathname, useRouter } from "next/navigation"; + +export default function GoToPlanButton({ planId }: { planId: string }) { + const router = useRouter(); + const pathname = usePathname(); + + function handleOnClick() { + router.push(`${pathname}/${planId}`); + } + + return ( + + ); +} diff --git a/src/app/components/building-passport/RecommendationCard.tsx b/src/app/components/building-passport/RecommendationCard.tsx index 0a9ccc6..a2b5148 100644 --- a/src/app/components/building-passport/RecommendationCard.tsx +++ b/src/app/components/building-passport/RecommendationCard.tsx @@ -1,5 +1,8 @@ "use client"; -import { ComponentRecommendation } from "@/app/db/schema/recommendations"; +import { + Recommendation, + RecommendationType, +} from "@/app/db/schema/recommendations"; import { Dispatch, SetStateAction, useState } from "react"; import { formatNumber } from "@/app/utils"; import { RecommendationMetricMap } from "@/types/recommendations"; @@ -10,6 +13,11 @@ const selectionStyling = const noSelectionStyling = "shadow active:shadow active:bg-brandmidblue w-full border rounded p-4 cursor-pointer text-gray-300 bg-white hover:bg-hoverblue hover:text-gray-100 transition-colors rounded-md flex flex-col justify-start"; +const TitleMap = { + wall_insulation: "Wall Insulation", + floor_insulation: "Floor Insulation", +}; + export default function RecommendationCard({ componentType, recommendationData, @@ -22,8 +30,8 @@ export default function RecommendationCard({ currentSapPoints, setExpectedEpcRating, }: { - componentType: string; - recommendationData: ComponentRecommendation[]; + componentType: RecommendationType; + recommendationData: Recommendation[]; setCostMap: Dispatch>; costMap: RecommendationMetricMap; setTotalEstimatedCost: Dispatch>; @@ -34,11 +42,11 @@ export default function RecommendationCard({ setExpectedEpcRating: Dispatch>; }) { const defaultComponent = recommendationData.find( - (rec: ComponentRecommendation) => rec.default - ) as ComponentRecommendation; + (rec: Recommendation) => rec.default + ) as Recommendation; const [cardComponent, setCardComponent] = - useState(defaultComponent); + useState(defaultComponent); const [modalIsOpen, setModalIsOpen] = useState(false); @@ -49,7 +57,7 @@ export default function RecommendationCard({ setModalIsOpen(true); }} > -

{componentType}

+

{TitleMap[componentType]}

{cardComponent ? ( cardComponent.description @@ -67,7 +75,7 @@ export default function RecommendationCard({ Estimated Cost: {cardComponent - ? "£" + formatNumber(cardComponent.estimatedCost) + ? "£" + formatNumber(cardComponent?.estimatedCost || 0) : ""} diff --git a/src/app/components/building-passport/RecommendationContainer.tsx b/src/app/components/building-passport/RecommendationContainer.tsx index d4ee30f..cccb5bb 100644 --- a/src/app/components/building-passport/RecommendationContainer.tsx +++ b/src/app/components/building-passport/RecommendationContainer.tsx @@ -1,8 +1,8 @@ "use client"; import { - ComponentRecommendation, Recommendation, + RecommendationType, } from "@/app/db/schema/recommendations"; import RecommendationCard from "./RecommendationCard"; import RecommendationCostSummaryCard from "./RecommendationCostSummaryCard"; @@ -10,12 +10,12 @@ 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]/recommendations/utils"; +import { sumRecommendationMetricMap } from "@/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils"; import { RecommendationMetricMap } from "@/types/recommendations"; import RecommendationEpcSummaryCard from "./RecommendationEpcSummaryCard"; interface RecommendationContainerProps { - recommendations: Recommendation; + recommendations: Recommendation[]; propertyMeta: PropertyMeta; } @@ -23,28 +23,40 @@ export default function RecommendationContainer({ recommendations, propertyMeta, }: RecommendationContainerProps) { - const defaultWallsRecommendations = recommendations.Walls?.find( - (rec: ComponentRecommendation) => rec.default - ) || { estimatedCost: 0, sapPoints: 0 }; + const categorizedRecommendations = recommendations.reduce((acc, curr) => { + const typeKey = curr.type as RecommendationType; - const defaultFloorRecommendations = recommendations.Floor?.find( - (rec: ComponentRecommendation) => rec.default - ) || { estimatedCost: 0, sapPoints: 0 }; + if (!acc[typeKey]) { + acc[typeKey] = []; + } + acc[typeKey].push(curr); + return acc; + }, {} as Record); - const defaultVentiliationRecommendations = recommendations.Ventilation?.find( - (rec: ComponentRecommendation) => rec.default - ) || { estimatedCost: 0, sapPoints: 0 }; + const defaultWallsRecommendations = + categorizedRecommendations.wall_insulation.find( + (rec: Recommendation) => rec.default + ) || { estimatedCost: 0, sapPoints: 0 }; + + const defaultFloorRecommendations = + categorizedRecommendations.floor_insulation?.find( + (rec: Recommendation) => rec.default + ) || { estimatedCost: 0, sapPoints: 0 }; + + // const defaultVentiliationRecommendations = recommendations.Ventilation?.find( + // (rec: ComponentRecommendation) => rec.default + // ) || { estimatedCost: 0, sapPoints: 0 }; const [costMap, setCostMap] = useState({ - Walls: defaultWallsRecommendations.estimatedCost, - Floor: defaultFloorRecommendations.estimatedCost, - Ventilation: defaultVentiliationRecommendations.estimatedCost, + Walls: defaultWallsRecommendations?.estimatedCost || 0, + Floor: defaultFloorRecommendations?.estimatedCost || 0, + // Ventilation: defaultVentiliationRecommendations?.estimatedCost || 0, }); const [sapMap, setSapMap] = useState({ - Walls: defaultWallsRecommendations.sapPoints, - Floor: defaultFloorRecommendations.sapPoints, - Ventilation: defaultVentiliationRecommendations.sapPoints, + Walls: defaultWallsRecommendations?.sapPoints || 0, + Floor: defaultFloorRecommendations.sapPoints || 0, + // Ventilation: defaultVentiliationRecommendations.sapPoints, }); const [totalEstimatedCost, setTotalEstimatedCost] = useState( @@ -58,7 +70,8 @@ export default function RecommendationContainer({ const currentEpcRating = propertyMeta.currentEpcRating; const currentSapPoints = propertyMeta.currentSapPoints; - const expectedSapPoints = currentSapPoints + totalSapPoints; + //TODO: Use Math.min while we have dummy SAP points + const expectedSapPoints = Math.min(currentSapPoints + totalSapPoints, 100); const [expectedEpcRating, setExpectedEpcRating] = useState( sapToEpc(expectedSapPoints) ); @@ -79,12 +92,13 @@ export default function RecommendationContainer({
- {Object.entries(recommendations).map( + {Object.entries(categorizedRecommendations).map( ([componentType, recommendationData], idx) => { return ( void; - recommendationData: ComponentRecommendation[]; - setCardComponent: Dispatch>; + recommendationData: Recommendation[]; + setCardComponent: Dispatch>; setCostMap: Dispatch>; costMap: RecommendationMetricMap; setTotalEstimatedCost: Dispatch>; @@ -88,8 +88,11 @@ export default function RecommendationModal({ const newSapImporvement = sumRecommendationMetricMap(newSapMap); setTotalSapPoints(newSapImporvement); + // TODO: While we have placeholder SAP points, constrain to 100 + const newSapPoints = Math.min(currentSapPoints + newSapImporvement, 100); + // update the expected EPC rating - setExpectedEpcRating(sapToEpc(currentSapPoints + newSapImporvement)); + setExpectedEpcRating(sapToEpc(newSapPoints)); } return ( diff --git a/src/app/components/building-passport/RecommendationTable.tsx b/src/app/components/building-passport/RecommendationTable.tsx index 1bff68a..97286e8 100644 --- a/src/app/components/building-passport/RecommendationTable.tsx +++ b/src/app/components/building-passport/RecommendationTable.tsx @@ -16,10 +16,10 @@ import { TableRow, } from "@/app/shadcn_components/ui/table"; -import { ComponentRecommendation } from "@/app/db/schema/recommendations"; +import { Recommendation } from "@/app/db/schema/recommendations"; import { Dispatch, SetStateAction, useEffect } from "react"; -interface DataTableProps { +interface DataTableProps { columns: ColumnDef[]; data: T[]; defaultRowIndex: number; @@ -34,7 +34,7 @@ interface DataTableProps { setSaveButtonDisabled: Dispatch>; } -export default function RecommendationTable({ +export default function RecommendationTable({ data, columns, defaultRowIndex, diff --git a/src/app/components/building-passport/RecommendationTableColumns.tsx b/src/app/components/building-passport/RecommendationTableColumns.tsx index 2eab268..4fb4b36 100644 --- a/src/app/components/building-passport/RecommendationTableColumns.tsx +++ b/src/app/components/building-passport/RecommendationTableColumns.tsx @@ -1,9 +1,9 @@ import { ColumnDef } from "@tanstack/react-table"; import { Checkbox } from "@/app/shadcn_components/ui/checkbox"; import { formatNumber } from "@/app/utils"; -import { ComponentRecommendation } from "@/app/db/schema/recommendations"; +import { Recommendation } from "@/app/db/schema/recommendations"; -const uvalueColumns: ColumnDef[] = [ +const uvalueColumns: ColumnDef[] = [ { accessorKey: "description", header: "Description", diff --git a/src/app/components/building-passport/Toolbar.tsx b/src/app/components/building-passport/Toolbar.tsx index ced6cf9..655c86d 100644 --- a/src/app/components/building-passport/Toolbar.tsx +++ b/src/app/components/building-passport/Toolbar.tsx @@ -46,10 +46,10 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) { const recommendationsButton = ( - Retrofit Recommendations + Retrofit Plans ); @@ -66,13 +66,13 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) { {preAssessmentReportButton} {recommendationsButton} - Plan optimiser - + */} + + + Portfolio + + { + columns: ColumnDef[]; + data: T[]; +} + +export default function PortfolioPlanTable< + T extends PortfolioPlanRecommendation +>({ data, columns }: DataTableProps) { + // Initialise the table + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }); + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ ); +} diff --git a/src/app/components/portfolio/plan/PlanTableColumns.tsx b/src/app/components/portfolio/plan/PlanTableColumns.tsx new file mode 100644 index 0000000..dee5a59 --- /dev/null +++ b/src/app/components/portfolio/plan/PlanTableColumns.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { PortfolioPlanRecommendation } from "@/app/db/schema/recommendations"; +import { formatNumber } from "@/app/utils"; +import { ColumnDef } from "@tanstack/react-table"; + +const formattedQuantity = { + m2: "m²", +}; + +const formattedMeasure = { + internal_wall_insulation: "Internal Wall Insulation", + external_wall_insulation: "External Wall Insulation", +}; + +export const portfolioPlanColumns: ColumnDef[] = [ + { + accessorKey: "materialType", + header: "Retrofit Measure", + cell: ({ row }) => { + return ( +
+ { + formattedMeasure[ + row.original.materialType as keyof typeof formattedMeasure + ] + } +
+ ); + }, + }, + { + accessorKey: "numberOfProperties", + header: "Number of properties", + cell: ({ row }) => { + return ( +
{row.original.numberOfProperties}
+ ); + }, + }, + { + accessorKey: "quantity", + header: "Quantity required", + cell: ({ row }) => { + return ( +
+ {row.original.quantity + + formattedQuantity[ + row.original.quantityUnit as keyof typeof formattedQuantity + ]} +
+ ); + }, + }, + { + accessorKey: "estimatedCost", + header: "Estimated Cost", + cell: ({ row }) => { + return
{"£" + formatNumber(row.original.estimatedCost)}
; + }, + }, +]; diff --git a/src/app/db/migrations/0035_perfect_tenebrous.sql b/src/app/db/migrations/0035_perfect_tenebrous.sql new file mode 100644 index 0000000..fb9dab1 --- /dev/null +++ b/src/app/db/migrations/0035_perfect_tenebrous.sql @@ -0,0 +1,6 @@ +ALTER TABLE "plan" ADD COLUMN "property_id" bigint NOT NULL;--> statement-breakpoint +DO $$ BEGIN + ALTER TABLE "plan" ADD CONSTRAINT "plan_property_id_property_id_fk" FOREIGN KEY ("property_id") REFERENCES "property"("id") ON DELETE no action ON UPDATE no action; +EXCEPTION + WHEN duplicate_object THEN null; +END $$; diff --git a/src/app/db/migrations/0036_real_stryfe.sql b/src/app/db/migrations/0036_real_stryfe.sql new file mode 100644 index 0000000..306555a --- /dev/null +++ b/src/app/db/migrations/0036_real_stryfe.sql @@ -0,0 +1,9 @@ +DO $$ BEGIN + CREATE TYPE "unit_quantity" AS ENUM('meters_squared'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +ALTER TABLE "recommendation_materials" ADD COLUMN "quantity" real NOT NULL;--> statement-breakpoint +ALTER TABLE "recommendation_materials" ADD COLUMN "quantity_unit" "unit_quantity" NOT NULL;--> statement-breakpoint +ALTER TABLE "recommendation_materials" ADD COLUMN "estimated_cost" real NOT NULL; \ No newline at end of file diff --git a/src/app/db/migrations/0037_awesome_harry_osborn.sql b/src/app/db/migrations/0037_awesome_harry_osborn.sql new file mode 100644 index 0000000..01a1588 --- /dev/null +++ b/src/app/db/migrations/0037_awesome_harry_osborn.sql @@ -0,0 +1 @@ +ALTER TYPE "unit_quantity" ADD VALUE 'm2'; \ No newline at end of file diff --git a/src/app/db/migrations/meta/0035_snapshot.json b/src/app/db/migrations/meta/0035_snapshot.json new file mode 100644 index 0000000..1cc6b1a --- /dev/null +++ b/src/app/db/migrations/meta/0035_snapshot.json @@ -0,0 +1,1272 @@ +{ + "version": "5", + "dialect": "pg", + "id": "976fba8c-569b-4257-a6fe-c0ae0d9e0e01", + "prevId": "2508c58f-bee6-44b6-9617-464c78540cf6", + "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 + }, + "depths": { + "name": "depths", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "depth_unit": { + "name": "depth_unit", + "type": "depth_unit", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "json", + "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 + } + }, + "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 + }, + "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 + } + }, + "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_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 + } + }, + "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 + } + }, + "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" + } + }, + "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" + } + }, + "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" + } + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/app/db/migrations/meta/0036_snapshot.json b/src/app/db/migrations/meta/0036_snapshot.json new file mode 100644 index 0000000..820b9ad --- /dev/null +++ b/src/app/db/migrations/meta/0036_snapshot.json @@ -0,0 +1,1296 @@ +{ + "version": "5", + "dialect": "pg", + "id": "2063f8e7-e1b2-486a-8e64-6a8a24b631d2", + "prevId": "976fba8c-569b-4257-a6fe-c0ae0d9e0e01", + "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 + }, + "depths": { + "name": "depths", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "depth_unit": { + "name": "depth_unit", + "type": "depth_unit", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "json", + "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 + } + }, + "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 + }, + "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 + } + }, + "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_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 + } + }, + "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" + } + }, + "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" + } + }, + "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": { + "meters_squared": "meters_squared" + } + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/app/db/migrations/meta/0037_snapshot.json b/src/app/db/migrations/meta/0037_snapshot.json new file mode 100644 index 0000000..b2d5069 --- /dev/null +++ b/src/app/db/migrations/meta/0037_snapshot.json @@ -0,0 +1,1296 @@ +{ + "version": "5", + "dialect": "pg", + "id": "ae896627-43c0-4d11-972f-a042af3bf845", + "prevId": "2063f8e7-e1b2-486a-8e64-6a8a24b631d2", + "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 + }, + "depths": { + "name": "depths", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "depth_unit": { + "name": "depth_unit", + "type": "depth_unit", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "json", + "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 + } + }, + "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 + }, + "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 + } + }, + "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_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 + } + }, + "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" + } + }, + "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" + } + }, + "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" + } + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/src/app/db/migrations/meta/_journal.json b/src/app/db/migrations/meta/_journal.json index 6d8a230..4ee5be6 100644 --- a/src/app/db/migrations/meta/_journal.json +++ b/src/app/db/migrations/meta/_journal.json @@ -246,6 +246,27 @@ "when": 1692012985856, "tag": "0034_wandering_nick_fury", "breakpoints": true + }, + { + "idx": 35, + "version": "5", + "when": 1692183485471, + "tag": "0035_perfect_tenebrous", + "breakpoints": true + }, + { + "idx": 36, + "version": "5", + "when": 1692623295104, + "tag": "0036_real_stryfe", + "breakpoints": true + }, + { + "idx": 37, + "version": "5", + "when": 1692623678223, + "tag": "0037_awesome_harry_osborn", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/db/schema/recommendations.ts b/src/app/db/schema/recommendations.ts index 7eb9ea2..40df900 100644 --- a/src/app/db/schema/recommendations.ts +++ b/src/app/db/schema/recommendations.ts @@ -8,24 +8,10 @@ import { real, boolean, bigint, + pgEnum, } from "drizzle-orm/pg-core"; import { material } from "./materials"; - -export interface ComponentRecommendation { - id: number; - type: string; - description: string; - estimatedCost: number; - default: boolean; - newUValue?: number; - sapPoints: number; -} - -export interface Recommendation { - Walls?: ComponentRecommendation[]; - Ventilation?: ComponentRecommendation[]; - Floor?: ComponentRecommendation[]; -} +import { InferModel, relations } from "drizzle-orm"; export const recommendation = pgTable("recommendation", { id: bigserial("id", { mode: "bigint" }).primaryKey(), @@ -51,6 +37,9 @@ export const recommendation = pgTable("recommendation", { totalWorkHours: real("total_work_hours"), }); +export const unitQuantity: [string, ...string[]] = ["m2"]; +export const unitQuantityEnum = pgEnum("unit_quantity", unitQuantity); + export const recommendationMaterials = pgTable("recommendation_materials", { id: bigserial("id", { mode: "bigint" }).primaryKey(), recommendationId: bigint("recommendation_id", { @@ -63,6 +52,9 @@ export const recommendationMaterials = pgTable("recommendation_materials", { .references(() => material.id), createdAt: timestamp("created_at").notNull().defaultNow(), depth: real("depth"), + quantity: real("quantity").notNull(), + quantityUnit: unitQuantityEnum("quantity_unit").notNull(), + estimatedCost: real("estimated_cost").notNull(), }); export const plan = pgTable("plan", { @@ -70,6 +62,9 @@ export const plan = pgTable("plan", { portfolioId: bigint("portfolio_id", { mode: "bigint" }) .notNull() .references(() => portfolio.id), + propertyId: bigint("property_id", { mode: "bigint" }) + .notNull() + .references(() => property.id), createdAt: timestamp("created_at").notNull().defaultNow(), isDefault: boolean("is_default").notNull(), }); @@ -85,3 +80,84 @@ export const planRecommendations = pgTable("plan_recommendations", { .notNull() .references(() => recommendation.id), }); + +// create a one to many relation to map a plan to the details in the underlying recommendation +// create a many to many map from a plan to a recommendation +// A recommendation can be in multiple plans and therefore we have a many to many relationship between +// plan and recommendations. This relationship is facilitated by the planRecommdnations table + +export const planRelations = relations(plan, ({ many }) => ({ + planRecommendations: many(planRecommendations), +})); + +export const planRecommendationsRelations = relations( + planRecommendations, + ({ one }) => ({ + plan: one(plan, { + fields: [planRecommendations.planId], + references: [plan.id], + }), + recommendation: one(recommendation, { + fields: [planRecommendations.recommendationId], + references: [recommendation.id], + }), + }) +); + +// We construct a relationship between a recommendation and recommendationMaterials +// On recommendationMaterial will map to a single recommendation + +// Define the relationships for the recommendation table +export const recommendationRelations = relations( + recommendation, + ({ many }) => ({ + recommendationMaterials: many(recommendationMaterials), + }) +); + +// Define the relationships for the material table +export const materialRelations = relations(material, ({ many }) => ({ + recommendationMaterials: many(recommendationMaterials), +})); + +// Define the relationships for the recommendationMaterials table +export const recommendationMaterialsRelations = relations( + recommendationMaterials, + ({ one }) => ({ + recommendation: one(recommendation, { + fields: [recommendationMaterials.recommendationId], + references: [recommendation.id], + }), + material: one(material, { + fields: [recommendationMaterials.materialId], + references: [material.id], + }), + }) +); + +export type Plan = InferModel; +export type Recommendation = InferModel; +export type PlanRecommendations = InferModel< + typeof planRecommendations, + "select" +>; + +// We allow recommendation types to be a string however we'll set up a typing for it here to control +// the types we expect +export type RecommendationType = "wall_insulation" | "floor_insulation"; + +export type UnnestedRecommendation = { + quantity: number; + quantityUnit: string; + estimatedCost: number; + materialType: string; + propertyId: string; +}; + +export interface PortfolioPlanRecommendation { + quantity: number; + quantityUnit: string; + estimatedCost: number; + materialType: string; + numberOfProperties: number; +} diff --git a/src/app/portfolio/[slug]/(portfolio)/layout.tsx b/src/app/portfolio/[slug]/(portfolio)/layout.tsx new file mode 100644 index 0000000..430a100 --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/layout.tsx @@ -0,0 +1,31 @@ +import { Toolbar } from "@/app/components/portfolio/Toolbar"; +import { getPortfolio } from "../utils"; + +export default async function PortfolioLayout({ + children, // will be a page or nested layout + params, +}: { + children: React.ReactNode; + params: { slug: string; propertyId: string }; +}) { + const portfolioId = params.slug; + const { name: portfolioName } = await getPortfolio(portfolioId); + + return ( +
+
+

+ {portfolioName} +

+
+
+
+
+ +
+
+
+ {children} +
+ ); +} diff --git a/src/app/portfolio/[slug]/page.tsx b/src/app/portfolio/[slug]/(portfolio)/page.tsx similarity index 91% rename from src/app/portfolio/[slug]/page.tsx rename to src/app/portfolio/[slug]/(portfolio)/page.tsx index f1bbd8c..0dcc3d8 100644 --- a/src/app/portfolio/[slug]/page.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/page.tsx @@ -1,5 +1,5 @@ import { HomeIcon } from "@heroicons/react/24/outline"; -import { getPortfolio, getProperties } from "./utils"; +import { getPortfolio, getProperties } from "../utils"; import { Toolbar } from "@/app/components/portfolio/Toolbar"; import DataTable from "@/app/portfolio/[slug]/components/propertyTable"; import { columns } from "@/app/portfolio/[slug]/components/propertyTableColumns"; @@ -187,12 +187,8 @@ export default async function Page({ }) { // This page is served from the server so we can make calls to the database - // This is temp until we retrieve this data from the frontend - // TODO: Update the objects to contains objective + any other required information - const portfolioId = params.slug; const { - name: portfolioName, budget, cost: totalCost, co2EquivalentSavings, @@ -212,18 +208,6 @@ export default async function Page({ return ( <> -
-

- {portfolioName} -

-
-
-
-
- -
-
-
diff --git a/src/app/portfolio/[slug]/(portfolio)/plan/page.tsx b/src/app/portfolio/[slug]/(portfolio)/plan/page.tsx new file mode 100644 index 0000000..74e8b32 --- /dev/null +++ b/src/app/portfolio/[slug]/(portfolio)/plan/page.tsx @@ -0,0 +1,25 @@ +import PortfolioPlanTable from "@/app/components/portfolio/plan/PlanTable"; +import { getPortfolioPlan } from "../../utils"; +import { portfolioPlanColumns } from "@/app/components/portfolio/plan/PlanTableColumns"; + +export default async function PortfolioPlan({ + params, +}: { + params: { slug: string }; +}) { + const portfolioId = params.slug; + const portfolioPlan = await getPortfolioPlan(portfolioId); + + return ( + <> +
+ { + + } +
+ + ); +} diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/[planId]/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/[planId]/page.tsx new file mode 100644 index 0000000..06d0a90 --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/[planId]/page.tsx @@ -0,0 +1,22 @@ +import { PropertyMeta } from "@/app/db/schema/property"; +import RecommendationContainer from "@/app/components/building-passport/RecommendationContainer"; +import { getPropertyMeta, getRecommendations } from "../../utils"; + +export default async function Recommendations({ + params, +}: { + params: { slug: string; propertyId: string; planId: string }; +}) { + const propertyMeta = await getPropertyMeta(params.propertyId); + const recommendations = await getRecommendations(params.planId); + + return ( +
+
Recommendations
+ +
+ ); +} diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/page.tsx new file mode 100644 index 0000000..dd5bd12 --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/page.tsx @@ -0,0 +1,99 @@ +import { getPlans, getPropertyMeta } from "../utils"; +import { formatDateTime, formatNumber, sapToEpc } from "@/app/utils"; +import EpcCard from "@/app/components/building-passport/EpcCard"; +import { Card, CardContent, CardHeader } from "@/app/shadcn_components/ui/card"; +import GoToPlanButton from "@/app/components/building-passport/GoToPlanButton"; + +function PlanCard({ + expectedEpcRating, + createdAt, + totalEstimatedCost, + totalSapPoints, + planId, +}: { + expectedEpcRating: string; + createdAt: Date; + totalEstimatedCost: number; + totalSapPoints: number; + planId: string; +}) { + return ( + +
+ +
+
+ + +
+ Total cost: + £{formatNumber(totalEstimatedCost)} +
+
+ Total SAP points: + {totalSapPoints} +
+
+
+
+
+ Created: {formatDateTime(createdAt)} +
+ +
+
+ ); +} + +export default async function RecommendationPlans({ + params, +}: { + params: { slug: string; propertyId: string }; +}) { + const propertyMeta = await getPropertyMeta(params.propertyId); + const plans = await getPlans(params.propertyId); + + // TODO: We don't currently have any visual identification of plans that have been set as default vs not + + return ( +
+
Retrofit Plans
+ +
+ {plans.map((plan, index) => { + const totalEstimatedCost = plan.planRecommendations.reduce( + (acc, rec) => acc + rec.recommendation.estimatedCost, + 0 + ); + const totalSapPoints = plan.planRecommendations.reduce( + (acc, rec) => acc + rec.recommendation.sapPoints, + 0 + ); + + // Placeholder while we return 999 for all sap points + const expectedSapPoints = Math.min( + propertyMeta.currentSapPoints + totalSapPoints, + 100 + ); + + const expectedEpcRating = sapToEpc(expectedSapPoints); + + return ( + + ); + })} +
+
+ ); +} diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/recommendations/utils.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils.tsx similarity index 100% rename from src/app/portfolio/[slug]/building-passport/[propertyId]/recommendations/utils.tsx rename to src/app/portfolio/[slug]/building-passport/[propertyId]/plans/utils.tsx diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/pre-assessment-report/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/pre-assessment-report/page.tsx index 0b6e031..86e280a 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/pre-assessment-report/page.tsx +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/pre-assessment-report/page.tsx @@ -21,7 +21,7 @@ import { function AddressCard({ address }: { address: string | null }) { // In the future, we might want to use react-wrap-balancer for some of this text return ( -
+
{address}
); @@ -35,6 +35,9 @@ interface PropertyDetailsCardProps { }; } +const rowTitleStyle = "text-gray-100 align-top pb-3"; +const rowValueStyle = "text-gray-100 text-end pr-8 pt-1 align-top pb-3"; + function PropertyDetailsCard({ conditionReportData, propertyMeta, @@ -45,33 +48,29 @@ function PropertyDetailsCard({ .join(" "); return ( -
-
+
+
- +
- - + + - - + + - - + - - + @@ -81,26 +80,20 @@ function PropertyDetailsCard({
Year built: - {propertyMeta.yearBuilt} - Year built:{propertyMeta.yearBuilt}
Property Type: - {propertyText} - Property Type:{propertyText}
Total floor area: + Total floor area: {`${conditionReportData.totalFloorArea} m`} 2
In conservation area: + In conservation area: {propertyDetailsSpatial.inConservationArea}
- - + + - - + + - - + + - - + diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/recommendations/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/recommendations/page.tsx deleted file mode 100644 index a47c737..0000000 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/recommendations/page.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Recommendation } from "@/app/db/schema/recommendations"; -import { PropertyMeta } from "@/app/db/schema/property"; -import RecommendationContainer from "@/app/components/building-passport/RecommendationContainer"; -import { getPropertyMeta } from "../utils"; - -export default async function Recommendations({ - params, -}: { - params: { slug: string; propertyId: string }; -}) { - const propertyMeta = await getPropertyMeta(params.propertyId); - - const recommendations: Recommendation = { - Walls: [ - { - id: 1, - type: "internal_wall_insulation", - description: "140mm Mineral Wool internal wall insulation", - estimatedCost: 9_450, - default: true, - newUValue: 0.29, - sapPoints: 4, - }, - { - id: 2, - type: "internal_wall_insulation", - description: "30mm Vacuum Insulation Panels wall insulation", - estimatedCost: 10_135, - default: false, - newUValue: 0.28, - sapPoints: 12, - }, - { - id: 3, - type: "internal_external_wall_insulation", - description: - "80mm Mineral Wool External Wall Insulation and 30mm rigid insulation internal wall insulation", - estimatedCost: 13_450, - default: false, - newUValue: 0.25, - sapPoints: 14, - }, - ], - Ventilation: [ - { - id: 4, - type: "mechanical_ventilation", - description: "Two decentralised mechanical ventilation units", - estimatedCost: 750, - default: true, - sapPoints: -2, - }, - ], - Floor: [ - { - id: 5, - type: "suspended_floor_insulation", - description: "70mm Rigid insulation foam boards with floor screed", - estimatedCost: 3_450, - default: true, - newUValue: 0.24, - sapPoints: 7, - }, - { - id: 5, - type: "suspended_floor_insulation", - description: "90mm Rigid insulation foam boards with floor screed", - estimatedCost: 4_120, - default: true, - newUValue: 0.24, - sapPoints: 7, - }, - ], - }; - - return ( -
-
Recommendations
- -
- ); -} diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/utils.ts b/src/app/portfolio/[slug]/building-passport/[propertyId]/utils.ts index 2cb729c..f7c8201 100644 --- a/src/app/portfolio/[slug]/building-passport/[propertyId]/utils.ts +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/utils.ts @@ -1,3 +1,8 @@ +import { recommendation } from "../../../../db/schema/recommendations"; +import { + Recommendation, + planRecommendations, +} from "@/app/db/schema/recommendations"; import { db } from "@/app/db/db"; import { Feature, @@ -6,9 +11,66 @@ import { PropertyMeta, propertyDetailsEpc, } from "@/app/db/schema/property"; +import { plan, Plan } from "@/app/db/schema/recommendations"; import { getRating } from "@/app/utils"; import { eq } from "drizzle-orm"; +type RecommendationList = { + recommendation: Recommendation; +}[]; + +export async function getRecommendations( + planId: string +): Promise { + const data = (await db.query.planRecommendations.findMany({ + where: eq(planRecommendations.planId, BigInt(planId)), + columns: {}, + with: { + recommendation: true, + }, + })) as RecommendationList; + + if (!data) { + throw new Error("Network response was not ok"); + } + + // unnest the recommendations + const recommendations = data.map((item) => item.recommendation); + + return recommendations; +} + +type PlanRelation = Plan & { + planRecommendations: { + recommendation: { estimatedCost: number; sapPoints: number }; + }[]; +}; + +export async function getPlans(propertyId: string): Promise { + const data = await db.query.plan.findMany({ + where: eq(plan.propertyId, BigInt(propertyId)), + with: { + planRecommendations: { + columns: {}, + with: { + recommendation: { + columns: { + estimatedCost: true, + sapPoints: true, + }, + }, + }, + }, + }, + }); + + if (!data) { + throw new Error("Network response was not ok"); + } + + return data as PlanRelation[]; +} + export async function getPropertyMeta( propertyId: string ): Promise { diff --git a/src/app/portfolio/[slug]/utils.ts b/src/app/portfolio/[slug]/utils.ts index 2c76306..ac170be 100644 --- a/src/app/portfolio/[slug]/utils.ts +++ b/src/app/portfolio/[slug]/utils.ts @@ -1,9 +1,15 @@ -import { eq } from "drizzle-orm"; +import { + recommendation, + UnnestedRecommendation, + PortfolioPlanRecommendation, +} from "./../../db/schema/recommendations"; +import { and, eq, inArray } from "drizzle-orm"; import { db } from "@/app/db/db"; import { portfolio } from "@/app/db/schema/portfolio"; import { property } from "@/app/db/schema/property"; import type { Portfolio } from "@/app/db/schema/portfolio"; import type { PropertyWithTarget } from "@/app/db/schema/property"; +import { plan, planRecommendations } from "@/app/db/schema/recommendations"; export async function getPortfolio(portfolioId: string): Promise { const data = await db @@ -50,3 +56,123 @@ export async function getProperties( return data; } + +interface UnaggregatedPortfolioPlanRecommendation { + quantity: number; + quantityUnit: string; + estimatedCost: number; + materialType: string; + propertyId?: string; + numberOfProperties?: number; // Optional field to hold the count of unique propertyIds +} + +function aggregateRecommendations( + data: UnaggregatedPortfolioPlanRecommendation[] +): PortfolioPlanRecommendation[] { + const grouped: { + [key: string]: PortfolioPlanRecommendation & { propertyIds: Set }; + } = {}; + + data.forEach((item) => { + // Use the combination of quantityUnit and materialType as a unique key + const key = `${item.quantityUnit}_${item.materialType}`; + + if (!grouped[key]) { + grouped[key] = { + ...item, + numberOfProperties: 0, // Initialize to 0 + propertyIds: item.propertyId ? new Set([item.propertyId]) : new Set(), + }; + } else { + grouped[key].quantity += item.quantity; + grouped[key].estimatedCost += item.estimatedCost; + if (item.propertyId) { + grouped[key].propertyIds.add(item.propertyId); + } + } + }); + + // Round the results to 2 decimal places and compute uniquePropertyCount + for (const key in grouped) { + grouped[key].quantity = parseFloat(grouped[key].quantity.toFixed(2)); + grouped[key].estimatedCost = parseFloat( + grouped[key].estimatedCost.toFixed(2) + ); + grouped[key].numberOfProperties = grouped[key].propertyIds.size; + delete (grouped[key] as any).propertyIds; // using type assertion to bypass the TS error + } + + return Object.values(grouped); +} + +export async function getPortfolioPlan(portfolioId: string) { + // To do this we need to do the following: + // 1. For the portfolioId, get all of the default plans. This can be done from the plan table + // 2. For the plans, get the recommendations. This can be done from the planRecommendation table + // 3. For the recommendations get the materials, the quantity and the cost. + // 4. For the materials, get the type of material + + // There was some trouble performing all of the relations in a single query so we split it into + // three requests (unfortunately) + + const plans = await db.query.plan.findMany({ + where: and( + eq(property.portfolioId, BigInt(portfolioId)), + eq(plan.isDefault, true) + ), + }); + + const planIds = plans.map((plan) => plan.id); + + const recommendations = await db.query.planRecommendations.findMany({ + where: inArray(planRecommendations.planId, planIds), + }); + + const recommendationIds = recommendations.map( + (recommendation) => recommendation.id + ); + + const data = await db.query.recommendation.findMany({ + where: and( + inArray(recommendation.id, recommendationIds), + eq(recommendation.default, true) + ), + columns: { propertyId: true }, + with: { + recommendationMaterials: { + with: { + material: { + columns: { + type: true, + }, + }, + }, + columns: { + quantity: true, + quantityUnit: true, + estimatedCost: true, + }, + }, + }, + }); + + const unnestedRecommendations: UnnestedRecommendation[] = data.reduce( + (acc: UnnestedRecommendation[], recommendation) => { + const materials = recommendation.recommendationMaterials.map( + (material) => ({ + quantity: material.quantity, + quantityUnit: material.quantityUnit, + estimatedCost: material.estimatedCost, + materialType: material.material.type, + propertyId: String(recommendation.propertyId), + }) + ); + return [...acc, ...materials]; + }, + [] + ); + + const aggregated = aggregateRecommendations(unnestedRecommendations); + + return aggregated; +} diff --git a/src/app/utils.ts b/src/app/utils.ts index 35c5c64..8a099ef 100644 --- a/src/app/utils.ts +++ b/src/app/utils.ts @@ -47,7 +47,7 @@ export function sapToEpc(sapPoints: number): string { } } -export function formatDateTime(dateTimeString: string): string { +export function formatDateTime(dateTimeString: string | Date): string { // Create a new Date object const dateTime = new Date(dateTimeString); diff --git a/src/types/recommendations.ts b/src/types/recommendations.ts index 09e7047..3934a0b 100644 --- a/src/types/recommendations.ts +++ b/src/types/recommendations.ts @@ -1,5 +1,6 @@ export interface RecommendationMetricMap { Walls: number; Floor: number; - Ventilation: number; + // TODO: Implement ventilation + // Ventilation: number; } diff --git a/tailwind.config.js b/tailwind.config.js index 9731b11..f70ada2 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -27,6 +27,7 @@ module.exports = { hoverblue: "#3e4073", brandtan: "#d3b488", hovertan: "#947750", + brandgold: "#f1bb06", brandbrown: "#3d1e05", brandmidblue: "#3943b7", border: "hsl(var(--border))",
Local Authority: - {propertyMeta.localAuthority} - Local Authority:{propertyMeta.localAuthority}
Constituency: - {propertyMeta.constituency} - Constituency:{propertyMeta.constituency}
Tenure - {propertyMeta.tenure} - Tenure{propertyMeta.tenure}
Number of rooms: + Number of rooms: {propertyMeta.numberOfRooms || "unkown"}