diff --git a/src/app/api/plan/trigger/route.ts b/src/app/api/plan/trigger/route.ts index 2925a61..9746b39 100644 --- a/src/app/api/plan/trigger/route.ts +++ b/src/app/api/plan/trigger/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { z } from "zod"; +import { MeasureKeyEnum } from "@/app/db/schema/recommendations"; const PresignedUrlBodySchema = z.object({ portfolio_id: z.string(), @@ -12,6 +13,12 @@ const PresignedUrlBodySchema = z.object({ budget: z.number().optional().nullable(), scenario_name: z.string().optional(), event_type: z.enum(["remote_assessment"]).optional(), + // inclusions is a list of measures, where the values are in measuresList + inclusions: z.array(MeasureKeyEnum).optional(), + exclusions: z.array(MeasureKeyEnum).optional(), + already_installed_file_path: z.string().optional(), + // optional scenario_id to link the plan to an existing scenario + scenario_id: z.string().optional().nullable(), }); export async function POST(request: NextRequest) { diff --git a/src/app/components/building-passport/Toolbar.tsx b/src/app/components/building-passport/Toolbar.tsx index 58866ed..0c7807a 100644 --- a/src/app/components/building-passport/Toolbar.tsx +++ b/src/app/components/building-passport/Toolbar.tsx @@ -6,6 +6,8 @@ import { HomeModernIcon, WrenchScrewdriverIcon, SunIcon, + CircleStackIcon, + BoltIcon, } from "@heroicons/react/24/outline"; import { NavigationMenu, @@ -35,7 +37,7 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) { href={`/portfolio/${portfolioId}/building-passport/${propertyId}/pre-assessment-report`} > - Pre-assessment Condition Report + Data ); @@ -44,18 +46,28 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) { className={navigationMenuTriggerStyle() + " ml-3 mr-2"} href={`/portfolio/${portfolioId}/building-passport/${propertyId}/energy-assessment`} > - + Energy Assessment ); + const documentsButton = ( + + + Documents + + ); + const solarAnalysisButton = ( - Solar Analysis + Solar ); @@ -76,13 +88,14 @@ export function Toolbar({ propertyId, portfolioId }: ToolbarProps) { href={`/portfolio/${portfolioId}/building-passport/${propertyId}`} > - Property Information + Summary {preAssessmentReportButton} {solarAnalysisButton} {recommendationsButton} + {documentsButton} {energyAssessmentsReportButton} companyInfo.id), +}); + +// --- buildings table --- +export const buildings = pgTable("buildings", { + id: uuid("id").primaryKey().defaultRandom(), + address: text("address").notNull(), + postcode: text("postcode").notNull(), + uprn: text("UPRN").notNull(), + landlordId: text("landlord_id").notNull(), + domnaId: text("domna_id").notNull(), +}); + +// --- documents table --- +export const documents = pgTable("documents", { + id: uuid("id").primaryKey().defaultRandom(), + + authorId: uuid("assessor_id") + .notNull() + .references(() => assessorInfo.id), + createdAt: timestamp("created_at", { withTimezone: true }).notNull(), + documentType: reportTypeEnum("document_type").notNull(), + + buildingId: uuid("building_id") + .notNull() + .references(() => buildings.id), + targetTable: text("target_table").notNull(), + targetId: uuid("target_id").notNull(), +}); + +export type Building = InferModel; +export type Document = InferModel; +export type AssessorInfo = InferModel; + +export type DocumentWithAuthor = Document & { + author: AssessorInfo; +}; + +export type BuildingWithDocuments = Building & { + documents: DocumentWithAuthor[]; +}; + +export type ReportType = (typeof reportType)[number]; diff --git a/src/app/db/documents_schema/relations.ts b/src/app/db/documents_schema/relations.ts new file mode 100644 index 0000000..927be39 --- /dev/null +++ b/src/app/db/documents_schema/relations.ts @@ -0,0 +1,22 @@ +import { pgTable, serial, text, integer } from "drizzle-orm/pg-core"; +import { relations } from "drizzle-orm"; +import { + buildings, + documents, + assessorInfo, +} from "@/app/db/documents_schema/documents"; + +export const buildingsRelations = relations(buildings, ({ many }) => ({ + documents: many(documents), +})); + +export const documentsRelations = relations(documents, ({ one }) => ({ + building: one(buildings, { + fields: [documents.buildingId], + references: [buildings.id], + }), + author: one(assessorInfo, { + fields: [documents.authorId], + references: [assessorInfo.id], + }), +})); diff --git a/src/app/db/migrations/0100_wakeful_doctor_doom.sql b/src/app/db/migrations/0100_wakeful_doctor_doom.sql new file mode 100644 index 0000000..27c0a87 --- /dev/null +++ b/src/app/db/migrations/0100_wakeful_doctor_doom.sql @@ -0,0 +1 @@ +ALTER TABLE "scenario" ADD COLUMN "goal_value" text; \ No newline at end of file diff --git a/src/app/db/migrations/meta/0100_snapshot.json b/src/app/db/migrations/meta/0100_snapshot.json new file mode 100644 index 0000000..312aeb2 --- /dev/null +++ b/src/app/db/migrations/meta/0100_snapshot.json @@ -0,0 +1,2988 @@ +{ + "version": "5", + "dialect": "pg", + "id": "55fba457-401b-4f68-9b8a-a674b1975251", + "prevId": "48db0dd4-acc9-4535-9b99-9fd566f87a91", + "tables": { + "energy_assessments": { + "name": "energy_assessments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uprn": { + "name": "uprn", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "uprn_source": { + "name": "uprn_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "property_type": { + "name": "property_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "building_reference_number": { + "name": "building_reference_number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_energy_efficiency": { + "name": "current_energy_efficiency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "current_energy_rating": { + "name": "current_energy_rating", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address1": { + "name": "address1", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address2": { + "name": "address2", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address3": { + "name": "address3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "posttown": { + "name": "posttown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "postcode": { + "name": "postcode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "county": { + "name": "county", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "constituency": { + "name": "constituency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "constituency_label": { + "name": "constituency_label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "low_energy_fixed_light_count": { + "name": "low_energy_fixed_light_count", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "construction_age_band": { + "name": "construction_age_band", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mainheat_energy_eff": { + "name": "mainheat_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "windows_env_eff": { + "name": "windows_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lighting_energy_eff": { + "name": "lighting_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_impact_potential": { + "name": "environment_impact_potential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mainheatcont_description": { + "name": "mainheatcont_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sheating_energy_eff": { + "name": "sheating_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_authority": { + "name": "local_authority", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "local_authority_label": { + "name": "local_authority_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fixed_lighting_outlets_count": { + "name": "fixed_lighting_outlets_count", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "energy_tariff": { + "name": "energy_tariff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mechanical_ventilation": { + "name": "mechanical_ventilation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "solar_water_heating_flag": { + "name": "solar_water_heating_flag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "co2_emissions_potential": { + "name": "co2_emissions_potential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "number_heated_rooms": { + "name": "number_heated_rooms", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "floor_description": { + "name": "floor_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "energy_consumption_potential": { + "name": "energy_consumption_potential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "built_form": { + "name": "built_form", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "number_open_fireplaces": { + "name": "number_open_fireplaces", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "windows_description": { + "name": "windows_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "glazed_area": { + "name": "glazed_area", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inspection_date": { + "name": "inspection_date", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": true + }, + "mains_gas_flag": { + "name": "mains_gas_flag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "co2_emiss_curr_per_floor_area": { + "name": "co2_emiss_curr_per_floor_area", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "heat_loss_corridor": { + "name": "heat_loss_corridor", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "unheated_corridor_length": { + "name": "unheated_corridor_length", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "flat_storey_count": { + "name": "flat_storey_count", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "roof_energy_eff": { + "name": "roof_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_floor_area": { + "name": "total_floor_area", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment_impact_current": { + "name": "environment_impact_current", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roof_description": { + "name": "roof_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "floor_energy_eff": { + "name": "floor_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "number_habitable_rooms": { + "name": "number_habitable_rooms", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hot_water_env_eff": { + "name": "hot_water_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mainheatc_energy_eff": { + "name": "mainheatc_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "main_fuel": { + "name": "main_fuel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lighting_env_eff": { + "name": "lighting_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "windows_energy_eff": { + "name": "windows_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "floor_env_eff": { + "name": "floor_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sheating_env_eff": { + "name": "sheating_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lighting_description": { + "name": "lighting_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "roof_env_eff": { + "name": "roof_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "walls_energy_eff": { + "name": "walls_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "photo_supply": { + "name": "photo_supply", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lighting_cost_potential": { + "name": "lighting_cost_potential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mainheat_env_eff": { + "name": "mainheat_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "multi_glaze_proportion": { + "name": "multi_glaze_proportion", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "main_heating_controls": { + "name": "main_heating_controls", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "flat_top_storey": { + "name": "flat_top_storey", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secondheat_description": { + "name": "secondheat_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "walls_env_eff": { + "name": "walls_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "transaction_type": { + "name": "transaction_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "extension_count": { + "name": "extension_count", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mainheatc_env_eff": { + "name": "mainheatc_env_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lmk_key": { + "name": "lmk_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wind_turbine_count": { + "name": "wind_turbine_count", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenure": { + "name": "tenure", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "floor_level": { + "name": "floor_level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "potential_energy_efficiency": { + "name": "potential_energy_efficiency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "potential_energy_rating": { + "name": "potential_energy_rating", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hot_water_energy_eff": { + "name": "hot_water_energy_eff", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "low_energy_lighting": { + "name": "low_energy_lighting", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "walls_description": { + "name": "walls_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hotwater_description": { + "name": "hotwater_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "co2_emissions_current": { + "name": "co2_emissions_current", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "heating_cost_current": { + "name": "heating_cost_current", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "heating_cost_potential": { + "name": "heating_cost_potential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hot_water_cost_current": { + "name": "hot_water_cost_current", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hot_water_cost_potential": { + "name": "hot_water_cost_potential", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lighting_cost_current": { + "name": "lighting_cost_current", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "energy_consumption_current": { + "name": "energy_consumption_current", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lodgement_date": { + "name": "lodgement_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "lodgement_datetime": { + "name": "lodgement_datetime", + "type": "timestamp (6)", + "primaryKey": false, + "notNull": true + }, + "mainheat_description": { + "name": "mainheat_description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "floor_height": { + "name": "floor_height", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "glazed_type": { + "name": "glazed_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_location": { + "name": "file_location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "surveyor_name": { + "name": "surveyor_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "surveyor_company": { + "name": "surveyor_company", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "space_heating_kwh": { + "name": "space_heating_kwh", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "water_heating_kwh": { + "name": "water_heating_kwh", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "number_of_doors": { + "name": "number_of_doors", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "number_of_insulated_doors": { + "name": "number_of_insulated_doors", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "number_of_floors": { + "name": "number_of_floors", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "insulation_wall_area": { + "name": "insulation_wall_area", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "heat_loss_perimeter": { + "name": "heat_loss_perimeter", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "party_wall_length": { + "name": "party_wall_length", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "perimeter": { + "name": "perimeter", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "rooms_with_bath_and_or_shower": { + "name": "rooms_with_bath_and_or_shower", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rooms_with_mixer_shower_no_bath": { + "name": "rooms_with_mixer_shower_no_bath", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "room_with_bath_and_mixer_shower": { + "name": "room_with_bath_and_mixer_shower", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "percent_draftproofed": { + "name": "percent_draftproofed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_hot_water_cylinder": { + "name": "has_hot_water_cylinder", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cylinder_insulation_type": { + "name": "cylinder_insulation_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cylinder_insulation_thickness": { + "name": "cylinder_insulation_thickness", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cylinder_thermostat": { + "name": "cylinder_thermostat", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "main_dwelling_ground_floor_area": { + "name": "main_dwelling_ground_floor_area", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "number_of_windows": { + "name": "number_of_windows", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "windows_area": { + "name": "windows_area", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "energy_assessment_documents": { + "name": "energy_assessment_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uprn": { + "name": "uprn", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "energy_assessment_id": { + "name": "energy_assessment_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "document_type": { + "name": "document_type", + "type": "document_type", + "primaryKey": false, + "notNull": true + }, + "document_location": { + "name": "document_location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "scenario_id": { + "name": "scenario_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "energy_assessment_documents_energy_assessment_id_energy_assessments_id_fk": { + "name": "energy_assessment_documents_energy_assessment_id_energy_assessments_id_fk", + "tableFrom": "energy_assessment_documents", + "tableTo": "energy_assessments", + "columnsFrom": [ + "energy_assessment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "energy_assessment_documents_scenario_id_energy_assessment_scenarios_id_fk": { + "name": "energy_assessment_documents_scenario_id_energy_assessment_scenarios_id_fk", + "tableFrom": "energy_assessment_documents", + "tableTo": "energy_assessment_scenarios", + "columnsFrom": [ + "scenario_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "energy_assessment_scenarios": { + "name": "energy_assessment_scenarios", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "scenario_name": { + "name": "scenario_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "energy_assessment_id": { + "name": "energy_assessment_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "energy_assessment_scenarios_energy_assessment_id_energy_assessments_id_fk": { + "name": "energy_assessment_scenarios_energy_assessment_id_energy_assessments_id_fk", + "tableFrom": "energy_assessment_scenarios", + "tableTo": "energy_assessments", + "columnsFrom": [ + "energy_assessment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "material": { + "name": "material", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "type", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "depth": { + "name": "depth", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "depth_unit": { + "name": "depth_unit", + "type": "depth_unit", + "primaryKey": false, + "notNull": false + }, + "cost_unit": { + "name": "cost_unit", + "type": "cost_unit", + "primaryKey": false, + "notNull": false + }, + "r_value_per_mm": { + "name": "r_value_per_mm", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "r_value_unit": { + "name": "r_value_unit", + "type": "r_value_unit", + "primaryKey": false, + "notNull": false + }, + "thermal_conductivity": { + "name": "thermal_conductivity", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "thermal_conductivity_unit": { + "name": "thermal_conductivity_unit", + "type": "thermal_conductivity_unit", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "prime_material_cost": { + "name": "prime_material_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "material_cost": { + "name": "material_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "labour_cost": { + "name": "labour_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "labour_hours_per_unit": { + "name": "labour_hours_per_unit", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "plant_cost": { + "name": "plant_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "total_cost": { + "name": "total_cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_installer_quote": { + "name": "is_installer_quote", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "portfolio": { + "name": "portfolio", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "budget": { + "name": "budget", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "status", + "primaryKey": false, + "notNull": true + }, + "goal": { + "name": "goal", + "type": "goal", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "number_of_properties": { + "name": "number_of_properties", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "co2_equivalent_savings": { + "name": "co2_equivalent_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "energy_savings": { + "name": "energy_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "energy_cost_savings": { + "name": "energy_cost_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "property_valuation_increase": { + "name": "property_valuation_increase", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "rental_yield_increase": { + "name": "rental_yield_increase", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "total_work_hours": { + "name": "total_work_hours", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "labour_days": { + "name": "labour_days", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (6) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "epc_breakdown_pre_retrofit": { + "name": "epc_breakdown_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "epc_breakdown_post_retrofit": { + "name": "epc_breakdown_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "n_units_to_retrofit": { + "name": "n_units_to_retrofit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "co2_per_unit_pre_retrofit": { + "name": "co2_per_unit_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "co2_per_unit_post_retrofit": { + "name": "co2_per_unit_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_bill_per_unit_pre_retrofit": { + "name": "energy_bill_per_unit_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_bill_per_unit_post_retrofit": { + "name": "energy_bill_per_unit_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_consumption_per_unit_pre_retrofit": { + "name": "energy_consumption_per_unit_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_consumption_per_unit_post_retrofit": { + "name": "energy_consumption_per_unit_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valuation_improvement_per_unit": { + "name": "valuation_improvement_per_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_per_unit": { + "name": "cost_per_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_per_co2_saved": { + "name": "cost_per_co2_saved", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_per_sap_point": { + "name": "cost_per_sap_point", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valuation_return_on_investment": { + "name": "valuation_return_on_investment", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "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": {} + }, + "non_intrusive_survey": { + "name": "non_intrusive_survey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uprn": { + "name": "uprn", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "survey_date": { + "name": "survey_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "surveyor": { + "name": "surveyor", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "non_intrusive_survey_notes": { + "name": "non_intrusive_survey_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "survey_id": { + "name": "survey_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note": { + "name": "note", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "non_intrusive_survey_notes_survey_id_non_intrusive_survey_id_fk": { + "name": "non_intrusive_survey_notes_survey_id_non_intrusive_survey_id_fk", + "tableFrom": "non_intrusive_survey_notes", + "tableTo": "non_intrusive_survey", + "columnsFrom": [ + "survey_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 + }, + "current_valuation": { + "name": "current_valuation", + "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 + }, + "current_energy_demand": { + "name": "current_energy_demand", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "current_energy_demand_heating_hotwater": { + "name": "current_energy_demand_heating_hotwater", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "estimated": { + "name": "estimated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "heating_cost_current": { + "name": "heating_cost_current", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "hot_water_cost_current": { + "name": "hot_water_cost_current", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "lighting_cost_current": { + "name": "lighting_cost_current", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "appliances_cost_current": { + "name": "appliances_cost_current", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "gas_standing_charge": { + "name": "gas_standing_charge", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "electricity_standing_charge": { + "name": "electricity_standing_charge", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "property_details_epc_property_id_property_id_fk": { + "name": "property_details_epc_property_id_property_id_fk", + "tableFrom": "property_details_epc", + "tableTo": "property", + "columnsFrom": [ + "property_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "property_details_epc_portfolio_id_portfolio_id_fk": { + "name": "property_details_epc_portfolio_id_portfolio_id_fk", + "tableFrom": "property_details_epc", + "tableTo": "portfolio", + "columnsFrom": [ + "portfolio_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "property_details_meter": { + "name": "property_details_meter", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uprn": { + "name": "uprn", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "energy_supplier": { + "name": "energy_supplier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gas_supplier": { + "name": "gas_supplier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "meter_reading_total": { + "name": "meter_reading_total", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "meter_reading_electricity": { + "name": "meter_reading_electricity", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "meter_reading_gas": { + "name": "meter_reading_gas", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "property_details_spatial": { + "name": "property_details_spatial", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "uprn": { + "name": "uprn", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "x_coordinate": { + "name": "x_coordinate", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "y_coordinate": { + "name": "y_coordinate", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "latitude": { + "name": "latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "longitude": { + "name": "longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "conservation_status": { + "name": "conservation_status", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_listed_building": { + "name": "is_listed_building", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_heritage_building": { + "name": "is_heritage_building", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "property_targets": { + "name": "property_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "property_id": { + "name": "property_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "portfolio_id": { + "name": "portfolio_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "epc": { + "name": "epc", + "type": "epc", + "primaryKey": false, + "notNull": false + }, + "heat_demand": { + "name": "heat_demand", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "property_targets_property_id_property_id_fk": { + "name": "property_targets_property_id_property_id_fk", + "tableFrom": "property_targets", + "tableTo": "property", + "columnsFrom": [ + "property_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "property_targets_portfolio_id_portfolio_id_fk": { + "name": "property_targets_portfolio_id_portfolio_id_fk", + "tableFrom": "property_targets", + "tableTo": "portfolio", + "columnsFrom": [ + "portfolio_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "plan": { + "name": "plan", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "portfolio_id": { + "name": "portfolio_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "property_id": { + "name": "property_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "scenario_id": { + "name": "scenario_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "valuation_increase_lower_bound": { + "name": "valuation_increase_lower_bound", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "valuation_increase_upper_bound": { + "name": "valuation_increase_upper_bound", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "valuation_increase_average": { + "name": "valuation_increase_average", + "type": "real", + "primaryKey": false, + "notNull": false + } + }, + "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" + }, + "plan_scenario_id_scenario_id_fk": { + "name": "plan_scenario_id_scenario_id_fk", + "tableFrom": "plan", + "tableTo": "scenario", + "columnsFrom": [ + "scenario_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 + }, + "measure_type": { + "name": "measure_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "kwh_savings": { + "name": "kwh_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "co2_equivalent_savings": { + "name": "co2_equivalent_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "energy_savings": { + "name": "energy_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "energy_cost_savings": { + "name": "energy_cost_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "property_valuation_increase": { + "name": "property_valuation_increase", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "rental_yield_increase": { + "name": "rental_yield_increase", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "total_work_hours": { + "name": "total_work_hours", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "labour_days": { + "name": "labour_days", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "already_installed": { + "name": "already_installed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": 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": {} + }, + "scenario": { + "name": "scenario", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "budget": { + "name": "budget", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "portfolio_id": { + "name": "portfolio_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "housing_type": { + "name": "housing_type", + "type": "housing_type", + "primaryKey": false, + "notNull": true + }, + "goal": { + "name": "goal", + "type": "goal", + "primaryKey": false, + "notNull": true + }, + "goal_value": { + "name": "goal_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "trigger_file_path": { + "name": "trigger_file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "already_installed_file_path": { + "name": "already_installed_file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "patches_file_path": { + "name": "patches_file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "non_invasive_recommendations_file_path": { + "name": "non_invasive_recommendations_file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "exclusions": { + "name": "exclusions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "multi_plan": { + "name": "multi_plan", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "total_work_hours": { + "name": "total_work_hours", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "energy_savings": { + "name": "energy_savings", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "co2_equivalent_savings": { + "name": "co2_equivalent_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 + }, + "labour_days": { + "name": "labour_days", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "epc_breakdown_pre_retrofit": { + "name": "epc_breakdown_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "epc_breakdown_post_retrofit": { + "name": "epc_breakdown_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number_of_properties": { + "name": "number_of_properties", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "n_units_to_retrofit": { + "name": "n_units_to_retrofit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "co2_per_unit_pre_retrofit": { + "name": "co2_per_unit_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "co2_per_unit_post_retrofit": { + "name": "co2_per_unit_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_bill_per_unit_pre_retrofit": { + "name": "energy_bill_per_unit_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_bill_per_unit_post_retrofit": { + "name": "energy_bill_per_unit_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_consumption_per_unit_pre_retrofit": { + "name": "energy_consumption_per_unit_pre_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "energy_consumption_per_unit_post_retrofit": { + "name": "energy_consumption_per_unit_post_retrofit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valuation_improvement_per_unit": { + "name": "valuation_improvement_per_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_per_unit": { + "name": "cost_per_unit", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_per_co2_saved": { + "name": "cost_per_co2_saved", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost_per_sap_point": { + "name": "cost_per_sap_point", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "valuation_return_on_investment": { + "name": "valuation_return_on_investment", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "scenario_portfolio_id_portfolio_id_fk": { + "name": "scenario_portfolio_id_portfolio_id_fk", + "tableFrom": "scenario", + "tableTo": "portfolio", + "columnsFrom": [ + "portfolio_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {} + }, + "solar": { + "name": "solar", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "longitude": { + "name": "longitude", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "latitude": { + "name": "latitude", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "uprn": { + "name": "uprn", + "type": "bigint", + "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()" + }, + "google_api_response": { + "name": "google_api_response", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {} + }, + "solar_scenario": { + "name": "solar_scenario", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "solar_id": { + "name": "solar_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "scenario_type": { + "name": "scenario_type", + "type": "scenario_type", + "primaryKey": false, + "notNull": true + }, + "number_panels": { + "name": "number_panels", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "array_kwhp": { + "name": "array_kwhp", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "lifetime_dc_kwh": { + "name": "lifetime_dc_kwh", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "yearly_dc_kwh": { + "name": "yearly_dc_kwh", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "lifetime_ac_kwh": { + "name": "lifetime_ac_kwh", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "yearly_ac_kwh": { + "name": "yearly_ac_kwh", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "expected_payback_years": { + "name": "expected_payback_years", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "panelled_roof_area": { + "name": "panelled_roof_area", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "solar_scenario_solar_id_solar_id_fk": { + "name": "solar_scenario_solar_id_solar_id_fk", + "tableFrom": "solar_scenario", + "tableTo": "solar", + "columnsFrom": [ + "solar_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": { + "document_type": { + "name": "document_type", + "values": { + "EPR": "EPR", + "Condition Report": "Condition Report", + "Evidence Report": "Evidence Report", + "Summary Information": "Summary Information", + "Floor Plan": "Floor Plan", + "Scenario Draft EPC": "Scenario Draft EPC", + "Scenario Site Notes": "Scenario Site Notes" + } + }, + "cost_unit": { + "name": "cost_unit", + "values": { + "gbp_sq_meter": "gbp_sq_meter", + "gbp_per_unit": "gbp_per_unit", + "gbp_per_m2": "gbp_per_m2", + "gbp_per_m": "gbp_per_m" + } + }, + "depth_unit": { + "name": "depth_unit", + "values": { + "mm": "mm" + } + }, + "type": { + "name": "type", + "values": { + "suspended_floor_insulation": "suspended_floor_insulation", + "solid_floor_insulation": "solid_floor_insulation", + "external_wall_insulation": "external_wall_insulation", + "internal_wall_insulation": "internal_wall_insulation", + "cavity_wall_insulation": "cavity_wall_insulation", + "mechanical_ventilation": "mechanical_ventilation", + "loft_insulation": "loft_insulation", + "exposed_floor_insulation": "exposed_floor_insulation", + "flat_roof_insulation": "flat_roof_insulation", + "room_roof_insulation": "room_roof_insulation", + "cavity_wall_extraction": "cavity_wall_extraction", + "iwi_wall_demolition": "iwi_wall_demolition", + "iwi_vapour_barrier": "iwi_vapour_barrier", + "iwi_redecoration": "iwi_redecoration", + "suspended_floor_demolition": "suspended_floor_demolition", + "suspended_floor_redecoration": "suspended_floor_redecoration", + "suspended_floor_vapour_barrier": "suspended_floor_vapour_barrier", + "solid_floor_demolition": "solid_floor_demolition", + "solid_floor_preparation": "solid_floor_preparation", + "solid_floor_vapour_barrier": "solid_floor_vapour_barrier", + "solid_floor_redecoration": "solid_floor_redecoration", + "ewi_wall_demolition": "ewi_wall_demolition", + "ewi_wall_preparation": "ewi_wall_preparation", + "ewi_wall_redecoration": "ewi_wall_redecoration", + "low_energy_lighting_installation": "low_energy_lighting_installation", + "flat_roof_preparation": "flat_roof_preparation", + "flat_roof_vapour_barrier": "flat_roof_vapour_barrier", + "flat_roof_waterproofing": "flat_roof_waterproofing", + "windows_glazing": "windows_glazing" + } + }, + "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", + "survey": "survey", + "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" + } + }, + "housing_type": { + "name": "housing_type", + "values": { + "Private": "Private", + "Social": "Social" + } + }, + "unit_quantity": { + "name": "unit_quantity", + "values": { + "m2": "m2", + "part": "part" + } + }, + "scenario_type": { + "name": "scenario_type", + "values": { + "unit": "unit", + "building": "building" + } + } + }, + "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 51f5753..3195bd0 100644 --- a/src/app/db/migrations/meta/_journal.json +++ b/src/app/db/migrations/meta/_journal.json @@ -701,6 +701,13 @@ "when": 1742138715788, "tag": "0099_faulty_nicolaos", "breakpoints": true + }, + { + "idx": 100, + "version": "5", + "when": 1749810028140, + "tag": "0100_wakeful_doctor_doom", + "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 9e88c4b..1a21ac4 100644 --- a/src/app/db/schema/recommendations.ts +++ b/src/app/db/schema/recommendations.ts @@ -13,7 +13,7 @@ import { } from "drizzle-orm/pg-core"; import { Material, material } from "./materials"; import { InferModel } from "drizzle-orm"; -import { ar } from "drizzle-orm/column.d-b7dc3bdb"; +import { z } from "zod"; export const recommendation = pgTable("recommendation", { id: bigserial("id", { mode: "bigint" }).primaryKey(), @@ -108,6 +108,7 @@ export const scenario = pgTable("scenario", { createdAt: timestamp("created_at").notNull().defaultNow(), housingType: housingTypeEnum("housing_type").notNull(), goal: goalEnum("goal").notNull(), + goalValue: text("goal_value"), triggerFilePath: text("trigger_file_path"), alreadyInstalledFilePath: text("already_installed_file_path"), patchesFilePath: text("patches_file_path"), @@ -232,3 +233,39 @@ export interface RecommendationWithMaterials { totalWorkHours: number; recommendationMaterials: RecommendationMaterialToMaterial[]; } + +export const measuresDisplayLabels = { + internal_wall_insulation: "Internal Wall Insulation", + external_wall_insulation: "External Wall Insulation", + cavity_wall_insulation: "Cavity Wall Insulation", + loft_insulation: "Loft Insulation", + flat_roof_insulation: "Flat Roof Insulation", + room_roof_insulation: "Room-in-Roof Insulation", + suspended_floor_insulation: "Suspended Floor Insulation", + solid_floor_insulation: "Solid Floor Insulation", + boiler_upgrade: "Boiler Upgrade", + high_heat_retention_storage_heater: "High Heat Retention Storage Heater", + air_source_heat_pump: "Air Source Heat Pump", + secondary_heating: "Secondary Heating", + solar_pv: "Solar PV", + double_glazing: "Double Glazing", + secondary_glazing: "Secondary Glazing", + ventilation: "Ventilation", + low_energy_lighting: "Low Energy Lighting", + fireplace: "Fireplace", + hot_water_tank_insulation: "Hot Water Tank Insulation", + cylinder_thermostat: "Cylinder Thermostat", +} as const; + +export type MeasureKey = keyof typeof measuresDisplayLabels; + +export const measuresList: MeasureKey[] = Object.keys( + measuresDisplayLabels +) as MeasureKey[]; + +export const MeasureKeyEnum = z.enum([ + ...Object.keys(measuresDisplayLabels), +] as [ + MeasureKey, // Force at least one measure key + ...MeasureKey[] +]); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4c9d8d1..5fb12ae 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -11,6 +11,7 @@ import { Toaster } from "@/app/shadcn_components/ui/toaster"; // If loading a variable font, you don't need to specify the font weight const inter = Inter({ subsets: ["latin"], + variable: "--font-inter", }); export const metadata = { diff --git a/src/app/portfolio/[slug]/(portfolio)/layout.tsx b/src/app/portfolio/[slug]/(portfolio)/layout.tsx index 430a100..2347700 100644 --- a/src/app/portfolio/[slug]/(portfolio)/layout.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/layout.tsx @@ -1,5 +1,5 @@ import { Toolbar } from "@/app/components/portfolio/Toolbar"; -import { getPortfolio } from "../utils"; +import { getPortfolio, getPortfolioScenarios } from "../utils"; export default async function PortfolioLayout({ children, // will be a page or nested layout @@ -10,6 +10,8 @@ export default async function PortfolioLayout({ }) { const portfolioId = params.slug; const { name: portfolioName } = await getPortfolio(portfolioId); + // We retrieve the scenarios associated with the portfolio + const scenarios = await getPortfolioScenarios(portfolioId); return (
@@ -21,7 +23,7 @@ export default async function PortfolioLayout({
- +
diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx new file mode 100644 index 0000000..5b52f10 --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentSection.tsx @@ -0,0 +1,109 @@ +"use client"; + +import React from "react"; +import { TableCell, TableRow } from "@/app/shadcn_components/ui/table"; +import { + DocumentWithAuthor, + ReportType, +} from "@/app/db/documents_schema/documents"; +import { BrandButton } from "@/app/components/Buttons"; +import { MenuButton } from "./MenuButton"; +import { useState } from "react"; +import { UploadModal } from "./UploadModal"; + +// Descriptions based on the document types +const descriptions: Record = { + QUIDOS_PRESITE_NOTE: + "Pre-site note from Quidos, detailing surveyor's findings", + CHARTED_SURVEYOR_REPORT: "Detailed report by a chartered surveyor", + ENERGY_PERFORMANCE_REPORT: "Energy performance breakdown", + U_VALUE_CALCULATOR_REPORT: "Calculated U-values for walls, floors, and roofs", + OVERWRITING_U_VALUE_DECLARATION_FORM: "Signed form for overwriting U-values", + OSMOSIS_CONDITION_PAS_2035_REPORT: + "Osmosis-generated PAS 2035 Condition Report", +}; + +export const DocumentSection = ({ + title, + docs, + sectionKey, + documentType, + fileTypes, +}: { + title: string; + docs: DocumentWithAuthor[]; + sectionKey: string; + documentType: ReportType; + fileTypes: ".xml,.pdf" | ".xml" | ".pdf"; +}) => { + const [showUploadModal, setShowUploadModal] = useState(false); + const [expanded, setExpanded] = useState(false); + const toggle = () => setExpanded((prev) => !prev); + + return ( + <> + + + {title} + + + + {docs.length > 0 ? ( + + ) : ( + No documents available + )} + + + + setShowUploadModal(true)} + backgroundColor="brandblue" + /> + + setShowUploadModal(false)} + documentType={documentType} + fileTypes={fileTypes} + /> + + + + {expanded && + docs.map((doc) => ( + + + {`Uploaded: ${doc.createdAt.toLocaleDateString("en-GB")}`} +
+ {descriptions[doc.documentType] ?? ""} +
+
+ + + {`Created by: ${ + doc.author.emailAddress ?? "No Author Information" + }`} + + + + { + console.log("View clicked for", doc.id); + }} + onDelete={() => { + console.log("Delete clicked for", doc.id); + }} + /> + +
+ ))} + + ); +}; diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx new file mode 100644 index 0000000..52c40d2 --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/DocumentsTable.tsx @@ -0,0 +1,136 @@ +"use client"; + +import React from "react"; +import { + Table, + TableBody, + TableRow, + TableCell, +} from "@/app/shadcn_components/ui/table"; +import { DocumentWithAuthor } from "@/app/db/documents_schema/documents"; +import { DocumentSection } from "./DocumentSection"; + +import { useMutation } from "@tanstack/react-query"; + +import { MenuButton } from "./MenuButton"; + +type Props = { + documents: DocumentWithAuthor[]; + // allowedTypes: (typeof DocumentType)[number][]; // Use the union type for allowedTypes as well +}; + +// Fetch the presigned URL from the API +async function generatePresignedUrl(fileKey: string) { + const response = await fetch("/api/energy-assessment-documents", { + method: "POST", + body: JSON.stringify({ fileKey }), + }); + + if (!response.ok) { + throw new Error("Failed to generate presigned URL"); + } + + const data = await response.json(); + return data.url; +} + +export const DocumentsTable: React.FC = ({ + documents, + // allowedTypes, +}) => { + const [expanded, setExpanded] = React.useState(false); + + // Mutation to handle the presigned URL generation + const { mutate: fetchPresignedUrl } = useMutation( + // Use the file key as the argument to generate the URL + async (fileKey: string) => await generatePresignedUrl(fileKey), + { + onSuccess: (url) => { + window.open(url, "_blank"); // Open the file in a new tab + }, + onError: (error) => { + console.error("Error generating presigned URL:", error); + }, + } + ); + + const handleDownload = () => { + // Generate URL and open in new tab + // fetchPresignedUrl(documentLocation); + console.log("Download button clicked"); + }; + + const handleUpload = () => { + // Handle the upload logic here + console.log("Upload button clicked"); + }; + + // We split out the various document types. Filter all of the quidos pre-site notes + const quidosPreSite = documents.filter( + (doc) => doc.documentType === "QUIDOS_PRESITE_NOTE" + ); + + const osmosisConditionReport = documents.filter( + (doc) => doc.documentType === "OSMOSIS_CONDITION_PAS_2035_REPORT" + ); + + const floors = documents.filter((doc) => doc.documentType === "FLOOR_PLAN"); + + const occupancy = documents.filter( + (doc) => doc.documentType === "OCCUPANCY_ASSESSMENT" + ); + + return ( + // Quidos Pre-Site Notes Row + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/MenuButton.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/MenuButton.tsx new file mode 100644 index 0000000..16fbe11 --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/MenuButton.tsx @@ -0,0 +1,34 @@ +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/app/shadcn_components/ui/dropdown-menu"; +import { MoreVertical } from "lucide-react"; +import { Button } from "@/app/shadcn_components/ui/button"; + +type Props = { + onView: () => void; + onDelete: () => void; +}; + +export const MenuButton: React.FC = ({ onView, onDelete }) => { + return ( + + + + + + View + + Delete + + + + ); +}; diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/UploadModal.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/UploadModal.tsx new file mode 100644 index 0000000..53ef373 --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/UploadModal.tsx @@ -0,0 +1,101 @@ +"use client"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogDescription, + DialogFooter, +} from "@/app/shadcn_components/ui/dialog"; +import { Button } from "@/app/shadcn_components/ui/button"; +import { ReportType } from "@/app/db/documents_schema/documents"; +import { Input } from "@/app/shadcn_components/ui/input"; +import { useState } from "react"; + +type UploadModalProps = { + open: boolean; + onClose: () => void; + documentType: string; + fileTypes: ".xml,.pdf" | ".xml" | ".pdf"; +}; + +const titles: Record = { + QUIDOS_PRESITE_NOTE: "RdSAP Summary Report", +}; + +export const UploadModal = ({ + open, + onClose, + documentType, + fileTypes = ".xml,.pdf", +}: UploadModalProps) => { + const [uploadFiles, setUploadFiles] = useState([]); + const [buttonDisabled, setButtonDisabled] = useState(true); + + function handleInputOnChange(e: React.ChangeEvent) { + if (e.target.files) { + const filesArray = Array.from(e.target.files); + const extensions = filesArray.map((file) => + file.name.split(".").pop()?.toLowerCase() + ); + // The valid extension are defined by filetypes e.g. ".xml,.pdf" so we split on the comma + const validExtensions = fileTypes + .split(",") + .map((ext) => ext.replace(".", "")); + + // Check if the files have valid extensions + const isValid = extensions.every((ext) => + validExtensions.includes(ext || "") + ); + + if (isValid) { + setUploadFiles(filesArray); + setButtonDisabled(false); + } else { + setButtonDisabled(true); + } + } else { + setButtonDisabled(true); + } + } + + return ( + + + + Upload Document + + Upload an {titles[documentType]}. Once uploaded, + automated extraction can begin. + + + +
+ +
+ + + + + +
+
+ ); +}; diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx new file mode 100644 index 0000000..b1183ae --- /dev/null +++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/documents/page.tsx @@ -0,0 +1,71 @@ +import { documentsDB } from "@/app/db/documents_db"; +import { + buildings, + DocumentWithAuthor, + BuildingWithDocuments, +} from "@/app/db/documents_schema/documents"; +import { getPropertyMeta } from "@/app/portfolio/[slug]/building-passport/[propertyId]/utils"; +import { eq } from "drizzle-orm"; +import { DocumentsTable } from "./DocumentsTable"; + +async function getDocuments( + uprn: number +): Promise { + const result = documentsDB.query.buildings.findFirst({ + where: eq(buildings.uprn, String(uprn)), + with: { + documents: { + with: { + author: true, // Include author information - there will only be one author per document + }, + }, + }, + }); + + // If we have no buildings, we return an empty object + if (!result) { + return { + id: "", + address: "", + postcode: "", + uprn: String(uprn), + landlordId: "", + domnaId: "", + documents: [] as DocumentWithAuthor[], + } as BuildingWithDocuments; + } + + return result; +} + +export default async function DocumentsPage({ + params, +}: { + params: { slug: string; propertyId: string }; +}) { + // Get the property UPRN + const propertyId = params.propertyId; + if (!propertyId || propertyId === "0") { + throw Error("Invalid propertyId"); + } + + const propertyMeta = await getPropertyMeta(propertyId); + const documents = await getDocuments(propertyMeta.uprn); + + return ( + <> +
+
+ Core Survey Documents +
+
+ +
+ +
+ Coordination +
+
+ + ); +} diff --git a/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx b/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx new file mode 100644 index 0000000..1da5392 --- /dev/null +++ b/src/app/portfolio/[slug]/components/RemoteAssessmentDropdowns.tsx @@ -0,0 +1,173 @@ +import { Menu, Transition } from "@headlessui/react"; +import { Fragment } from "react"; +import { Button } from "@/app/shadcn_components/ui/button"; +import { PlusIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; +import { Float } from "@headlessui-float/react"; + +export type Option = { label: string; value: string; disabled?: boolean }; + +// Extend ScenarioOption to include extra metadata +export type ScenarioOption = { + label: string; // scenario name + value: string; // scenario value + housingType?: string; // existing scenario housing type + goal?: string; // existing scenario goal + goalValue?: string; // existing scenario goal value +}; + +interface ScenarioSelectProps { + selectedValue: string | null; + onSelect: (option: ScenarioOption) => void; + scenarios: ScenarioOption[]; +} + +interface SelectDropdownProps { + options: Option[]; + selectedOption: string; + onSelectOption: (opt: Option) => void; +} + +export function SelectScenarioDropdown({ + selectedValue, + onSelect, + scenarios, +}: ScenarioSelectProps) { + const newOption: ScenarioOption = { + label: "New scenario", + value: "__new__", + }; + const options = [newOption, ...scenarios]; + + const selectedLabel = + options.find((o) => o.value === selectedValue)?.label || "Choose scenario"; + + return ( + + + {selectedValue === newOption.value && ( + + + + + {options.map((opt) => ( + + {({ active }) => ( + + )} + + ))} + + + + ); +} + +export function SelectDropdown({ + options, + selectedOption, + onSelectOption, +}: { + options: Option[]; + selectedOption: string; + onSelectOption: (opt: Option) => void; +}) { + const label = + options.find((o) => o.value === selectedOption)?.label || "Select…"; + + return ( + + + {label} + + + + + + {options.map((opt) => ( + + {({ active, disabled }) => ( + + )} + + ))} + + + + ); +} diff --git a/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx b/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx index 8b119d9..c411ac8 100644 --- a/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx +++ b/src/app/portfolio/[slug]/components/RemoteAssessmentModal.tsx @@ -4,11 +4,9 @@ import { Dialog, Transition, Menu } from "@headlessui/react"; import { Fragment, useMemo } from "react"; import { Input } from "@/app/shadcn_components/ui/input"; import { Button } from "@/app/shadcn_components/ui/button"; -import { Float } from "@headlessui-float/react"; -import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/20/solid"; import { useMutation } from "@tanstack/react-query"; import { useSession } from "next-auth/react"; -import { useForm, FormProvider } from "react-hook-form"; +import { useForm, FormProvider, useFormContext } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { @@ -20,11 +18,22 @@ import { FormDescription, } from "@/app/shadcn_components/ui/form"; import { useToast } from "@/app/hooks/use-toast"; +import { ScenarioSelect } from "@/app/db/schema/recommendations"; +import { useState } from "react"; +import { + SelectScenarioDropdown, + SelectDropdown, +} from "./RemoteAssessmentDropdowns"; +import { + measuresList, + measuresDisplayLabels, + MeasureKeyEnum, +} from "@/app/db/schema/recommendations"; type Option = { label: string; value: string; - disabled: boolean; + disabled?: boolean; }; type DropdownProps = { @@ -148,124 +157,26 @@ interface EngineTriggerBody { multi_plan: boolean; budget: null; event_type: string; + inclusions: (typeof measuresList)[number][]; + scenario_id?: string | null; } const formSchema = z.object({ - scenario: z.string().min(1, "Scenario is required"), - goal: z.string().min(1, "Goal is required"), - goalValue: z.string().min(1, "Goal value is required"), - housingType: z.string().min(1, "Housing type is required"), - addressLineOne: z.string().min(1, "Address is required"), - postcode: z.string().min(1, "Postcode is required"), - uprn: z.number().min(1, "UPRN is required"), - valuation: z.number().min(1, "Valuation is required"), - // Both property type and build form are optional - propertyType: z.string().optional().nullable(), - builtForm: z.string().optional().nullable(), + scenario: z.string().min(1), + goal: z.string().min(1), + goalValue: z.string().min(1), + housingType: z.string().min(1), + addressLineOne: z.string().min(1), + postcode: z.string().min(1), + uprn: z.number().min(1), + valuation: z.number().min(1), + propertyType: z.string().nullable(), + builtForm: z.string().nullable(), + measures: z.array(MeasureKeyEnum).min(1, "Select at least one measure."), }); type FormValues = z.infer; -export function SelectDropdown({ - options, - selectedOption, - onSelectOption, -}: DropdownProps) { - return ( - - - - {selectedOption || "Select an option"} - - - - {options.map((option) => ( - - {({ active }) => ( - - )} - - ))} - - - - - ); -} - -export function SelectUpDropdown({ - options, - selectedOption, - onSelectOption, - width = "w-1/2", -}: OptionalDropdownProps) { - const menuButtonStyle = (width = "w-full") => - `inline-flex justify-center ${width} px-4 py-2 text-sm font-medium text-white bg-brandblue rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75`; - - return ( - - - - {selectedOption || "Select an option"} - - - - {options.map((option) => ( - - {({ active }) => ( - - )} - - ))} - - - - - ); -} - async function uploadCsvToS3({ presignedUrl, file, @@ -365,14 +276,18 @@ function useCreateRemoteAssessment({ valuation, propertyType, builtForm, + measures, + scenarioId, }: { portfolioId: string; uprn: number | null; addressLineOne: string; postcode: string; valuation: string | number | null; + measures: (typeof measuresList)[number][]; propertyType?: string | null; builtForm?: string | null; + scenarioId?: string | null; }) { // 1) We want to upload the asset data. To do this, we format the asset data, generate a presigned URL, and upload the data to S3. // 2) We then want to upload valuation data. To do this, we format the valuation data, generate a presigned URL, and upload the data to S3. @@ -455,6 +370,7 @@ function useCreateRemoteAssessment({ async function triggerEngine(data: FormValues) { try { const triggerBody: EngineTriggerBody = { + scenario_id: scenarioId || null, portfolio_id: portfolioId, housing_type: data.housingType, goal: data.goal, @@ -465,11 +381,14 @@ function useCreateRemoteAssessment({ non_invasive_recommendations_file_path: "", valuation_file_path: valuationDataFileKey, scenario_name: data.scenario, + inclusions: data.measures, multi_plan: true, budget: null, event_type: "remote_assessment", }; + console.log("Triggering engine with body:", triggerBody); + const response = await fetch("/api/plan/trigger", { method: "POST", headers: { @@ -520,16 +439,37 @@ function useCreateRemoteAssessment({ } export default function RemoteAssessmentModal({ - portfolioId, isOpen, setIsOpen, + portfolioId, + scenarios, }: { isOpen: boolean; - setIsOpen: (isOpen: boolean) => void; + setIsOpen: (open: boolean) => void; portfolioId: string; + scenarios: ScenarioSelect[]; }) { - const form = useForm({ + const NEW_SENTINEL = "__new__"; + const [selectedScenario, setSelectedScenario] = useState(null); + const { toast } = useToast(); + const [showMeasures, setShowMeasures] = useState(false); + + const scenarioOptions: Option[] = useMemo( + () => [ + ...scenarios.map((s) => ({ + label: s.name || "", + value: String(s.id) || "", + disabled: false, + })), + ], + [scenarios] + ); + + console.log("Scenario options:", scenarioOptions); + + const form = useForm({ resolver: zodResolver(formSchema), + mode: "onChange", defaultValues: { scenario: "", housingType: "", @@ -539,45 +479,69 @@ export default function RemoteAssessmentModal({ postcode: "", uprn: 0, valuation: 0, + propertyType: null as string | null, + builtForm: null as string | null, + measures: measuresList, }, }); + const { reset, setValue, formState } = form; + const { isValid, isSubmitting } = formState; - // const [toastState, setToastState] = useState([]); - const { toast } = useToast(); + const measures = form.watch("measures"); - const onSubmit = async (data: FormValues) => { - try { - await handleSubmit(data); - form.reset(); - setIsOpen(false); - toast({ - title: "The Remote Assesment has been sent", - description: "", + const { + handleSubmit: triggerAssessment, + presignedUrlIsLoading, + presignedUrlIsError, + } = useCreateRemoteAssessment({ + portfolioId, + uprn: form.watch("uprn"), + addressLineOne: form.watch("addressLineOne"), + postcode: form.watch("postcode"), + valuation: form.watch("valuation"), + propertyType: form.watch("propertyType"), + builtForm: form.watch("builtForm"), + measures: measures, + scenarioId: selectedScenario, + }); + + const onSelectScenario = (opt: Option) => { + setSelectedScenario(opt.value); + if (opt.value === NEW_SENTINEL) { + reset({ + ...form.getValues(), + scenario: "", + housingType: "", + goal: "", + goalValue: "", }); - } catch (error) { - console.error("Error submitting form:", error); + } else { + const picked = scenarios.find((s) => String(s.id) === opt.value); + + if (!picked) return; + setValue("scenario", picked.name || ""); + setValue("housingType", picked.housingType); + setValue("goal", picked.goal); + setValue("goalValue", picked.goalValue || ""); } }; - const { handleSubmit, presignedUrlIsLoading, presignedUrlIsError } = - useCreateRemoteAssessment({ - portfolioId, - uprn: form.watch("uprn"), - addressLineOne: form.watch("addressLineOne"), - postcode: form.watch("postcode"), - valuation: form.watch("valuation"), - propertyType: form.watch("propertyType"), - builtForm: form.watch("builtForm"), - }); + const onSubmit = form.handleSubmit(async (data) => { + console.log("Triggered"); + await triggerAssessment(data); + form.reset(); + setIsOpen(false); + toast({ title: "Remote assessment sent" }); + }); return ( - <> - - setIsOpen(false)} - > + + setIsOpen(false)} + > +
-
+ -
-
- - - - Remote Assessment Details - - -
-
-
- + + +
+
); } function setIsOpen(arg0: boolean) { throw new Error("Function not implemented."); } + +function MeasuresCheckboxes({ + form, +}: { + form: ReturnType>; +}) { + const { control } = form; + const allMeasures = measuresList; + return ( +
+
+ + +
+ + {/* Measures grid */} +
+ {measuresList.map((measure) => ( + ( + + { + const checked = e.target.checked; + const current = new Set(field.value ?? []); + if (checked) { + current.add(measure); + } else { + current.delete(measure); + } + field.onChange(Array.from(current)); + }} + className="h-4 w-4" + /> + + {measuresDisplayLabels[measure]} + + + )} + /> + ))} +
+
+ ); +} diff --git a/src/app/portfolio/[slug]/utils.ts b/src/app/portfolio/[slug]/utils.ts index c40c70a..40a4e4b 100644 --- a/src/app/portfolio/[slug]/utils.ts +++ b/src/app/portfolio/[slug]/utils.ts @@ -68,6 +68,18 @@ export async function getPortfolio(portfolioId: string): Promise { return data[0]; } +export async function getPortfolioScenarios( + portfolioId: string +): Promise { + // This function will grab all scenarios from the database for a given portfolio + const scenarios = await db + .select() + .from(scenario) + .where(eq(scenario.portfolioId, BigInt(portfolioId))); + + return scenarios; +} + export async function getPortfolioPerformance( portfolioId: string ): Promise { diff --git a/tailwind.config.js b/tailwind.config.js index 80e8463..df92c55 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -102,7 +102,7 @@ module.exports = { hovertan: "#947750", brandgold: "#f1bb06", hovergold: "#c79d12", - brandbrown: "#3d1e05", + brandbrown: "#c4a47c", brandmidblue: "#3943b7", brandlightblue: "#00a9f4", border: "hsl(var(--border))", @@ -144,7 +144,7 @@ module.exports = { hoverblue: "#3e4073", brandtan: "#d3b488", hovertan: "#947750", - brandbrown: "#3d1e05", + brandbrown: "#c4a47c", brandmidblue: "#3943b7", brandlightblue: "#00a9f4", },