From e0f1b4f871917361a6089da2af7392fcc1ae309c Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Wed, 15 Apr 2026 14:43:30 +0000
Subject: [PATCH 01/43] dockerfile
---
.devcontainer/Dockerfile | 12 ++++++++
.devcontainer/docker-compose.yml | 6 ++--
src/app/components/portfolio/AddNew.tsx | 39 ++++++++++++++++++++++++-
3 files changed, 53 insertions(+), 4 deletions(-)
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index dc8ed3de..42f93fac 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -1,6 +1,9 @@
FROM library/python:3.12-bullseye
ARG USER=vscode
+ARG USER_UID=1000
+ARG USER_GID=1000
+
ARG DEBIAN_FRONTEND=noninteractive
# Install system dependencies in a single layer
@@ -37,6 +40,15 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
# RUN apt-get install terraform
# RUN terraform -install-autocomplete
+# Install Claude
+USER ${USER}
+RUN curl -fsSL https://claude.ai/install.sh | bash \
+ && export PATH="/home/${USER}/.local/bin:${PATH}" \
+ && claude plugin marketplace add JuliusBrussee/caveman \
+ && claude plugin install caveman@caveman
+ENV PATH="/home/vscode/.local/bin:${PATH}"
+USER root
+
# Set the working directory
WORKDIR /workspaces/assessment-model
\ No newline at end of file
diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml
index b6b65093..1c8e315e 100644
--- a/.devcontainer/docker-compose.yml
+++ b/.devcontainer/docker-compose.yml
@@ -1,11 +1,11 @@
-version: "3.8"
-
services:
frontend:
- user: "${UID}:${GID}"
build:
context: ..
dockerfile: .devcontainer/Dockerfile
+ args:
+ USER_UID: ${UID:-1000}
+ USER_GID: ${GID:-1000}
command: sleep infinity
ports:
- "3000:3000"
diff --git a/src/app/components/portfolio/AddNew.tsx b/src/app/components/portfolio/AddNew.tsx
index 5a3bafd2..88595073 100644
--- a/src/app/components/portfolio/AddNew.tsx
+++ b/src/app/components/portfolio/AddNew.tsx
@@ -7,11 +7,13 @@ import {
DocumentMagnifyingGlassIcon,
ChevronDownIcon,
DocumentPlusIcon,
+ RectangleStackIcon,
} from "@heroicons/react/24/outline";
import { cn } from "@/lib/utils";
import { useRouter } from "next/navigation";
import { Dispatch, SetStateAction, useState } from "react";
+import BulkUploadComingSoonModal from "@/app/components/portfolio/BulkUploadComingSoonModal";
interface AddNewProps {
portfolioId: string;
@@ -26,6 +28,7 @@ export default function AddNew({
}: AddNewProps) {
const router = useRouter();
const [loadingRemote, setLoadingRemote] = useState(false);
+ const [isBulkUploadOpen, setIsBulkUploadOpen] = useState(false);
function handleRemoteAssessment() {
setLoadingRemote(true);
@@ -33,6 +36,12 @@ export default function AddNew({
}
return (
+ <>
+ setIsBulkUploadOpen(false)}
+ portfolioId={portfolioId}
+ />
+ >
);
}
From 224a8ad6964cbd60292f5744051b7b475ecf3d15 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Thu, 16 Apr 2026 16:59:30 +0000
Subject: [PATCH 02/43] uplaod
---
src/app/db/db.ts | 2 +
.../db/migrations/0170_furry_moonstone.sql | 11 +
src/app/db/migrations/meta/0170_snapshot.json | 6410 +++++++++++++++++
src/app/db/migrations/meta/_journal.json | 7 +
4 files changed, 6430 insertions(+)
create mode 100644 src/app/db/migrations/0170_furry_moonstone.sql
create mode 100644 src/app/db/migrations/meta/0170_snapshot.json
diff --git a/src/app/db/db.ts b/src/app/db/db.ts
index 52b10c12..e973a875 100644
--- a/src/app/db/db.ts
+++ b/src/app/db/db.ts
@@ -15,6 +15,7 @@ import { subTasks } from "@/app/db/schema/tasks/subtask";
import * as CrmSchema from "@/app/db/schema/crm/hubspot_deal_table";
import * as UploadedFilesSchema from "@/app/db/schema/uploaded_files";
import * as PortfolioOrgSchema from "@/app/db/schema/portfolio_organisation";
+import * as BulkAddressUploadsSchema from "@/app/db/schema/bulk_address_uploads";
export const pool = new Pool({
host: process.env.DB_HOST,
@@ -41,6 +42,7 @@ const schema = {
...CrmSchema,
...UploadedFilesSchema,
...PortfolioOrgSchema,
+ ...BulkAddressUploadsSchema,
};
export const db = drizzle(pool, {
diff --git a/src/app/db/migrations/0170_furry_moonstone.sql b/src/app/db/migrations/0170_furry_moonstone.sql
new file mode 100644
index 00000000..4c9a968b
--- /dev/null
+++ b/src/app/db/migrations/0170_furry_moonstone.sql
@@ -0,0 +1,11 @@
+CREATE TABLE "bulk_address_uploads" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "portfolio_id" text NOT NULL,
+ "user_id" text NOT NULL,
+ "s3_bucket" text NOT NULL,
+ "s3_key" text NOT NULL,
+ "filename" text NOT NULL,
+ "status" text DEFAULT 'ready_for_processing' NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
diff --git a/src/app/db/migrations/meta/0170_snapshot.json b/src/app/db/migrations/meta/0170_snapshot.json
new file mode 100644
index 00000000..7f5cbb24
--- /dev/null
+++ b/src/app/db/migrations/meta/0170_snapshot.json
@@ -0,0 +1,6410 @@
+{
+ "id": "f5fcb7de-81c2-42a0-8775-abccb47a8216",
+ "prevId": "74d30cc2-134d-4780-a8df-c1b42667c420",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.postcode_search": {
+ "name": "postcode_search",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_data": {
+ "name": "result_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_updated_at": {
+ "name": "last_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postcode_search_postcode_unique": {
+ "name": "postcode_search_postcode_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "postcode"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bulk_address_uploads": {
+ "name": "bulk_address_uploads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_bucket": {
+ "name": "s3_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_key": {
+ "name": "s3_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filename": {
+ "name": "filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready_for_processing'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.aspect_condition": {
+ "name": "aspect_condition",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "element_id": {
+ "name": "element_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_type": {
+ "name": "aspect_type",
+ "type": "aspect_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_instance": {
+ "name": "aspect_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quantity": {
+ "name": "quantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "install_date": {
+ "name": "install_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "renewal_year": {
+ "name": "renewal_year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "comments": {
+ "name": "comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "aspect_condition_element_id_element_id_fk": {
+ "name": "aspect_condition_element_id_element_id_fk",
+ "tableFrom": "aspect_condition",
+ "tableTo": "element",
+ "columnsFrom": [
+ "element_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.element": {
+ "name": "element",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "survey_id": {
+ "name": "survey_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_type": {
+ "name": "element_type",
+ "type": "element_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_instance": {
+ "name": "element_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "element_survey_id_property_condition_survey_id_fk": {
+ "name": "element_survey_id_property_condition_survey_id_fk",
+ "tableFrom": "element",
+ "tableTo": "property_condition_survey",
+ "columnsFrom": [
+ "survey_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_condition_survey": {
+ "name": "property_condition_survey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_company_data": {
+ "name": "hubspot_company_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "company_name": {
+ "name": "company_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_deal_data": {
+ "name": "hubspot_deal_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deal_id": {
+ "name": "deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dealname": {
+ "name": "dealname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dealstage": {
+ "name": "dealstage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "project_code": {
+ "name": "project_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "listing_id": {
+ "name": "listing_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome_notes": {
+ "name": "outcome_notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_description": {
+ "name": "major_condition_issue_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_photos": {
+ "name": "major_condition_issue_photos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_evidence_s3_url": {
+ "name": "major_condition_issue_evidence_s3_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_status": {
+ "name": "coordination_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_status": {
+ "name": "design_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pashub_link": {
+ "name": "pashub_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sharepoint_link": {
+ "name": "sharepoint_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dampmould_growth": {
+ "name": "dampmould_growth",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pre_sap": {
+ "name": "pre_sap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordinator": {
+ "name": "coordinator",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_completion_date": {
+ "name": "mtp_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_re_model_completion_date": {
+ "name": "mtp_re_model_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ioe_v3_completion_date": {
+ "name": "ioe_v3_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_measures": {
+ "name": "proposed_measures",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approved_package": {
+ "name": "approved_package",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "designer": {
+ "name": "designer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_type": {
+ "name": "design_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_completion_date": {
+ "name": "design_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actual_measures_installed": {
+ "name": "actual_measures_installed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer": {
+ "name": "installer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer_handover": {
+ "name": "installer_handover",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_status": {
+ "name": "lodgement_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measures_lodgement_date": {
+ "name": "measures_lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_commencement_date": {
+ "name": "expected_commencement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_comments": {
+ "name": "coordination_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyor": {
+ "name": "surveyor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "damp_mould_and_repairs_comments": {
+ "name": "damp_mould_and_repairs_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_date": {
+ "name": "confirmed_survey_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_time": {
+ "name": "confirmed_survey_time",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyed_date": {
+ "name": "surveyed_date",
+ "type": "timestamp (6) with time zone",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_status_tracker": {
+ "name": "property_status_tracker",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "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 (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": {
+ "property_status_tracker_property_id_property_id_fk": {
+ "name": "property_status_tracker_property_id_property_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_status_tracker_portfolio_id_portfolio_id_fk": {
+ "name": "property_status_tracker_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_store": {
+ "name": "epc_store",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api_created_at": {
+ "name": "epc_api_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api": {
+ "name": "epc_api",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_created_at": {
+ "name": "epc_page_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page": {
+ "name": "epc_page",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_rrn": {
+ "name": "epc_page_rrn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_epc_store_uprn": {
+ "name": "uq_epc_store_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.files_from_surveyor": {
+ "name": "files_from_surveyor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_json_url": {
+ "name": "s3_json_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "files_from_surveyor_portfolio_id_portfolio_id_fk": {
+ "name": "files_from_surveyor_portfolio_id_portfolio_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "files_from_surveyor_property_id_property_id_fk": {
+ "name": "files_from_surveyor_property_id_property_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package": {
+ "name": "funding_package",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheme": {
+ "name": "scheme",
+ "type": "scheme",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "project_funding": {
+ "name": "project_funding",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_uplift": {
+ "name": "total_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "full_project_score": {
+ "name": "full_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_plan_id_plan_id_fk": {
+ "name": "funding_package_plan_id_plan_id_fk",
+ "tableFrom": "funding_package",
+ "tableTo": "plan",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package_measures": {
+ "name": "funding_package_measures",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "funding_package_id": {
+ "name": "funding_package_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure": {
+ "name": "measure",
+ "type": "type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "material_id": {
+ "name": "material_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "innovation_uplift": {
+ "name": "innovation_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_measures_funding_package_id_funding_package_id_fk": {
+ "name": "funding_package_measures_funding_package_id_funding_package_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "funding_package",
+ "columnsFrom": [
+ "funding_package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "funding_package_measures_material_id_material_id_fk": {
+ "name": "funding_package_measures_material_id_material_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "material",
+ "columnsFrom": [
+ "material_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.inspections": {
+ "name": "inspections",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "archetype": {
+ "name": "archetype",
+ "type": "inspection_archetype",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archetype_2": {
+ "name": "archetype_2",
+ "type": "inspection_archetype_2",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_construction": {
+ "name": "wall_construction",
+ "type": "inspections_wall_construction",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation": {
+ "name": "insulation",
+ "type": "inspections_wall_insulation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation_material": {
+ "name": "insulation_material",
+ "type": "inspections_insulation_material",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "borescoped": {
+ "name": "borescoped",
+ "type": "inspection_borescoped",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_orientation": {
+ "name": "roof_orientation",
+ "type": "inspections_roof_orientation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tile_hung": {
+ "name": "tile_hung",
+ "type": "inspections_tile_hung",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rendered": {
+ "name": "rendered",
+ "type": "inspections_rendered",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cladding": {
+ "name": "cladding",
+ "type": "inspections_cladding",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_issues": {
+ "name": "access_issues",
+ "type": "inspections_access_issues",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "surveyor_name": {
+ "name": "surveyor_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "inspections_property_id_property_id_fk": {
+ "name": "inspections_property_id_property_id_fk",
+ "tableFrom": "inspections",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.material": {
+ "name": "material",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "type",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_unit": {
+ "name": "cost_unit",
+ "type": "cost_unit",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "innovation_rate": {
+ "name": "innovation_rate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "size": {
+ "name": "size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "size_unit": {
+ "name": "size_unit",
+ "type": "size_unit",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "includes_scaffolding": {
+ "name": "includes_scaffolding",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "includes_battery": {
+ "name": "includes_battery",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "battery_size": {
+ "name": "battery_size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organisation": {
+ "name": "organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "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()"
+ },
+ "hubspot_company_id": {
+ "name": "hubspot_company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.portfolio_organisation": {
+ "name": "portfolio_organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organisation_id": {
+ "name": "organisation_id",
+ "type": "uuid",
+ "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": {
+ "portfolio_organisation_portfolio_id_portfolio_id_fk": {
+ "name": "portfolio_organisation_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "portfolio_organisation_organisation_id_organisation_id_fk": {
+ "name": "portfolio_organisation_organisation_id_organisation_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "organisation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_organisation_portfolio_id_unique": {
+ "name": "portfolio_organisation_portfolio_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "building_reference_number": {
+ "name": "building_reference_number",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "installed_measures_sap_point_adjustment": {
+ "name": "installed_measures_sap_point_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_sap_points_adjusted_for_installed_measures": {
+ "name": "is_sap_points_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "original_sap_points": {
+ "name": "original_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_sap_points": {
+ "name": "lodged_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_epc_rating": {
+ "name": "lodged_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_portfolio_uprn": {
+ "name": "uq_property_portfolio_uprn",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"property\".\"uprn\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_expired": {
+ "name": "is_expired",
+ "type": "boolean",
+ "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
+ },
+ "sap_05_overwritten": {
+ "name": "sap_05_overwritten",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "sap_05_score": {
+ "name": "sap_05_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_05_epc_rating": {
+ "name": "sap_05_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": 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
+ },
+ "original_co2_emissions": {
+ "name": "original_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_primary_energy_consumption": {
+ "name": "original_primary_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand": {
+ "name": "original_current_energy_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand_heating_hotwater": {
+ "name": "original_current_energy_demand_heating_hotwater",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_co2_adjustment": {
+ "name": "installed_measures_co2_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_energy_demand_adjustment": {
+ "name": "installed_measures_energy_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_total_energy_bill_adjustment": {
+ "name": "installed_measures_total_energy_bill_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_heat_demand_adjustment": {
+ "name": "installed_measures_heat_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_epc_adjusted_for_installed_measures": {
+ "name": "is_epc_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "lodged_co2_emissions": {
+ "name": "lodged_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_heat_demand": {
+ "name": "lodged_heat_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_been_remodelled": {
+ "name": "has_been_remodelled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "environment_impact_current": {
+ "name": "environment_impact_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_details_epc_property_portfolio": {
+ "name": "uq_property_details_epc_property_portfolio",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "uq_property_details_spatial_uprn": {
+ "name": "uq_property_details_spatial_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.installed_measure": {
+ "name": "installed_measure",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_type": {
+ "name": "measure_type",
+ "type": "measure_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "installed_at": {
+ "name": "installed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "sap_points": {
+ "name": "sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "carbon_savings": {
+ "name": "carbon_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kwh_savings": {
+ "name": "kwh_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bill_savings": {
+ "name": "bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_demand_savings": {
+ "name": "heat_demand_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "idx_installed_measure_uprn": {
+ "name": "idx_installed_measure_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_active": {
+ "name": "idx_installed_measure_uprn_active",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_measure_type": {
+ "name": "idx_installed_measure_measure_type",
+ "columns": [
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_measure": {
+ "name": "idx_installed_measure_uprn_measure",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "post_sap_points": {
+ "name": "post_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_epc_rating": {
+ "name": "post_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_co2_emissions": {
+ "name": "post_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_savings": {
+ "name": "co2_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_bill": {
+ "name": "post_energy_bill",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_bill_savings": {
+ "name": "energy_bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_consumption": {
+ "name": "post_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_consumption_savings": {
+ "name": "energy_consumption_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_post_retrofit": {
+ "name": "valuation_post_retrofit",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_increase": {
+ "name": "valuation_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_of_works": {
+ "name": "cost_of_works",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contingency_cost": {
+ "name": "contingency_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plan_type": {
+ "name": "plan_type",
+ "type": "plan_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_plan_portfolio_scenario": {
+ "name": "idx_plan_portfolio_scenario",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_latest_per_property": {
+ "name": "idx_plan_latest_per_property",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "idx_plan_recommendations_plan_id": {
+ "name": "idx_plan_recommendations_plan_id",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_recommendations_plan_rec": {
+ "name": "idx_plan_recommendations_plan_rec",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "contingency_cost": {
+ "name": "contingency_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": {
+ "recommendation_property_id_idx": {
+ "name": "recommendation_property_id_idx",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_defaults": {
+ "name": "idx_recommendation_active_defaults",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_id_property": {
+ "name": "idx_recommendation_active_id_property",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": false
+ },
+ "quantity_unit": {
+ "name": "quantity_unit",
+ "type": "unit_quantity",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "recommendation_materials_recommendation_id_idx": {
+ "name": "recommendation_materials_recommendation_id_idx",
+ "columns": [
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": "cascade",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal_value": {
+ "name": "goal_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ashp_cop": {
+ "name": "ashp_cop",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2.8
+ },
+ "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
+ },
+ "contingency": {
+ "name": "contingency",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funding": {
+ "name": "funding",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sub_task": {
+ "name": "sub_task",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cloud_logs_url": {
+ "name": "cloud_logs_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sub_task_task_id_tasks_id_fk": {
+ "name": "sub_task_task_id_tasks_id_fk",
+ "tableFrom": "sub_task",
+ "tableTo": "tasks",
+ "columnsFrom": [
+ "task_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_source": {
+ "name": "task_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "service": {
+ "name": "service",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team": {
+ "name": "team",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "uuid",
+ "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": {
+ "team_org_id_organisation_id_fk": {
+ "name": "team_org_id_organisation_id_fk",
+ "tableFrom": "team",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "org_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_members": {
+ "name": "team_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "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": {
+ "team_members_user_id_user_id_fk": {
+ "name": "team_members_user_id_user_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_members_team_id_team_id_fk": {
+ "name": "team_members_team_id_team_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_portfolio_permissions": {
+ "name": "team_portfolio_permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "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": {
+ "team_portfolio_permissions_team_id_team_id_fk": {
+ "name": "team_portfolio_permissions_team_id_team_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_portfolio_permissions_portfolio_id_portfolio_id_fk": {
+ "name": "team_portfolio_permissions_portfolio_id_portfolio_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.uploaded_files": {
+ "name": "uploaded_files",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "s3_file_bucket": {
+ "name": "s3_file_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_file_key": {
+ "name": "s3_file_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_upload_timestamp": {
+ "name": "s3_upload_timestamp",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_listing_id": {
+ "name": "hubspot_listing_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_type": {
+ "name": "file_type",
+ "type": "file_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_source": {
+ "name": "file_source",
+ "type": "file_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_id": {
+ "name": "oauth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_provider": {
+ "name": "oauth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "onboarded": {
+ "name": "onboarded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "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": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_profiles": {
+ "name": "user_profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_type": {
+ "name": "user_type",
+ "type": "user_profiles_user_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_count": {
+ "name": "property_count",
+ "type": "user_profiles_property_count",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goals": {
+ "name": "goals",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referral_source": {
+ "name": "referral_source",
+ "type": "user_profiles_referral_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nrla_membership_id": {
+ "name": "nrla_membership_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accepted_privacy": {
+ "name": "accepted_privacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accepted_privacy_at": {
+ "name": "accepted_privacy_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "marketing_opt_in": {
+ "name": "marketing_opt_in",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "marketing_opt_in_at": {
+ "name": "marketing_opt_in_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_name": {
+ "name": "last_name",
+ "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": {
+ "user_profiles_user_id_user_id_fk": {
+ "name": "user_profiles_user_id_user_id_fk",
+ "tableFrom": "user_profiles",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verificationToken": {
+ "name": "verificationToken",
+ "schema": "",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": [
+ "identifier",
+ "token"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.whlg": {
+ "name": "whlg",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.aspect_type": {
+ "name": "aspect_type",
+ "schema": "public",
+ "values": [
+ "material",
+ "condition",
+ "type",
+ "area",
+ "configuration",
+ "presence",
+ "risk",
+ "severity",
+ "location",
+ "finish",
+ "insulation",
+ "pointing",
+ "spalling",
+ "lintels",
+ "cladding",
+ "category",
+ "quantity",
+ "adequacy",
+ "rating",
+ "strategy",
+ "extent",
+ "distribution",
+ "structure",
+ "covering",
+ "fire_rating",
+ "external_decoration",
+ "work_required",
+ "age_band",
+ "construction_type",
+ "classification",
+ "system"
+ ]
+ },
+ "public.element_type": {
+ "name": "element_type",
+ "schema": "public",
+ "values": [
+ "property",
+ "property_construction_type",
+ "property_classification",
+ "property_age_band",
+ "storey_count",
+ "floor_level",
+ "floor_level_front_door",
+ "accessible_housing_register",
+ "asbestos",
+ "quality_standard",
+ "ccu",
+ "passenger_lift",
+ "stairlift",
+ "disabled_hoist_tracking",
+ "disabled_facilities",
+ "steps_to_front_door",
+ "roof",
+ "pitched_roof_covering",
+ "flat_roof_covering",
+ "rainwater_goods",
+ "loft_insulation",
+ "porch_canopy",
+ "chimney",
+ "fascia",
+ "soffit",
+ "fascia_soffit_bargeboards",
+ "gutters",
+ "store_roof",
+ "garage_roof",
+ "garage_and_store_roof",
+ "external_wall",
+ "external_noise_insulation",
+ "primary_wall",
+ "secondary_wall",
+ "downpipes",
+ "external_decoration",
+ "cladding",
+ "spandrel_panels",
+ "garage_walls",
+ "party_wall_fire_break",
+ "external_brickwork_pointing",
+ "internal_downpipes_external_area",
+ "external_windows",
+ "communal_windows",
+ "secondary_glazing",
+ "store_windows",
+ "garage_windows",
+ "garage_and_store_windows",
+ "external_door",
+ "front_door",
+ "rear_door",
+ "store_door",
+ "garage_door",
+ "garage_and_store_door",
+ "communal_entrance_door",
+ "main_door",
+ "block_entrance_door",
+ "lintel",
+ "patio_french_door",
+ "door_entry_handset",
+ "paths_and_hardstandings",
+ "parking_areas",
+ "boundary_walls",
+ "front_fencing",
+ "rear_fencing",
+ "side_fencing",
+ "rear_gate",
+ "front_gate",
+ "gates",
+ "retaining_walls",
+ "private_balcony",
+ "balcony_balustrade",
+ "outbuildings",
+ "garage_structure",
+ "paving",
+ "roads",
+ "soil_and_vent",
+ "solar_thermals",
+ "drop_kerb",
+ "outbuilding_overhaul",
+ "external_structural_defects",
+ "access_ramp",
+ "kitchen",
+ "kitchen_space_layout",
+ "tenant_installed_kitchen",
+ "kitchen_extractor_fan",
+ "bathroom",
+ "secondary_bathroom",
+ "secondary_toilet",
+ "bathroom_extractor_fan",
+ "additional_wc_or_whb",
+ "bathroom_remaining_life_source",
+ "kitchen_remaining_life_source",
+ "central_heating",
+ "heating_boiler",
+ "heating_distribution",
+ "secondary_heating",
+ "hot_water_system",
+ "cold_water_storage",
+ "heating_system",
+ "boiler_fuel",
+ "water_heating",
+ "programmable_heating",
+ "community_heating",
+ "gas_available",
+ "heat_recovery_units",
+ "heating_improvements",
+ "electrical_wiring",
+ "consumer_unit",
+ "smoke_detection",
+ "heat_detection",
+ "carbon_monoxide_detection",
+ "fire_door_rating",
+ "fire_risk_assessment",
+ "internal_wiring",
+ "electrics",
+ "communal_heating",
+ "communal_boiler",
+ "communal_electrics",
+ "communal_fire_alarm",
+ "communal_emergency_lighting",
+ "communal_door_entry",
+ "communal_cctv",
+ "communal_bin_store",
+ "communal_bin_store_doors",
+ "communal_bin_store_walls",
+ "communal_bin_store_roof",
+ "communal_refuse_chute",
+ "communal_floor_covering",
+ "communal_kitchen",
+ "communal_bathroom",
+ "communal_toilets",
+ "communal_gates",
+ "communal_lift",
+ "communal_passenger_lift",
+ "communal_balcony_walkway",
+ "communal_entrance",
+ "communal_internal_decorations",
+ "communal_internal_floor",
+ "communal_walkways",
+ "communal_external_doors",
+ "communal_stairs",
+ "communal_aerial",
+ "communal_aov",
+ "communal_internal_doors",
+ "communal_lateral_mains",
+ "communal_lighting",
+ "communal_lighting_conductor",
+ "communal_store_roof",
+ "communal_store_walls",
+ "communal_store_doors",
+ "communal_warden_call_system",
+ "communal_bms",
+ "communal_booster_pump",
+ "communal_dry_riser",
+ "communal_wet_riser",
+ "communal_cold_water_storage",
+ "communal_sprinkler",
+ "communal_plug_sockets",
+ "communal_circulation_space",
+ "ffhh_damp",
+ "ffhh_hold_and_cold_water",
+ "ffhh_drainage_lavatories",
+ "ffhh_neglected",
+ "ffhh_natural_light",
+ "ffhh_ventilation",
+ "ffhh_food_prep_and_washup",
+ "ffhh_unsafe_layout",
+ "ffhh_unstable_building",
+ "hhsrs_damp_and_mould",
+ "hhsrs_excess_cold",
+ "hhsrs_excess_heat",
+ "hhsrs_asbestos_and_mmf",
+ "hhsrs_biocides",
+ "hhsrs_carbon_monoxide",
+ "hhsrs_lead",
+ "hhsrs_radiation",
+ "hhsrs_uncombusted_fuel_gas",
+ "hhsrs_volatile_organic_compounds",
+ "hhsrs_crowding_and_space",
+ "hhsrs_entry_by_intruders",
+ "hhsrs_lighting",
+ "hhsrs_noise",
+ "hhsrs_domestic_hygiene_pests_refuse",
+ "hhsrs_food_safety",
+ "hhsrs_personal_hygiene_sanitation",
+ "hhsrs_water_supply",
+ "hhsrs_falls_associated_with_baths",
+ "hhsrs_falls_on_level_surfaces",
+ "hhsrs_falls_on_stairs",
+ "hhsrs_falls_between_levels",
+ "hhsrs_electrical_hazards",
+ "hhsrs_fire",
+ "hhsrs_flames_hot_surfaces",
+ "hhsrs_collision_and_entrapment",
+ "hhsrs_collision_hazards_low_headroom",
+ "hhsrs_explosions",
+ "hhsrs_ergonomics",
+ "hhsrs_structural_collapse",
+ "hhsrs_amenities"
+ ]
+ },
+ "public.document_type": {
+ "name": "document_type",
+ "schema": "public",
+ "values": [
+ "EPR",
+ "Condition Report",
+ "Evidence Report",
+ "Summary Information",
+ "Floor Plan",
+ "Scenario Draft EPC",
+ "Scenario Site Notes"
+ ]
+ },
+ "public.scheme": {
+ "name": "scheme",
+ "schema": "public",
+ "values": [
+ "eco4",
+ "gbis",
+ "whlg",
+ "none"
+ ]
+ },
+ "public.inspection_archetype_2": {
+ "name": "inspection_archetype_2",
+ "schema": "public",
+ "values": [
+ "detached",
+ "mid-terrace",
+ "enclosed mid-terrace",
+ "end-terrace",
+ "enclosed end-terrace",
+ "semi-detached"
+ ]
+ },
+ "public.inspection_archetype": {
+ "name": "inspection_archetype",
+ "schema": "public",
+ "values": [
+ "Bungalow",
+ "Flat",
+ "Maisonette",
+ "House",
+ "non-domestic"
+ ]
+ },
+ "public.inspection_borescoped": {
+ "name": "inspection_borescoped",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "refused"
+ ]
+ },
+ "public.inspections_access_issues": {
+ "name": "inspections_access_issues",
+ "schema": "public",
+ "values": [
+ "see notes",
+ "damp issues",
+ "foliage on walls",
+ "bushes against wall",
+ "trees around/anove property",
+ "high rise block flats/maisonettes",
+ "conservatory",
+ "lean-to",
+ "garage",
+ "extension",
+ "decking",
+ "shed against wall"
+ ]
+ },
+ "public.inspections_cladding": {
+ "name": "inspections_cladding",
+ "schema": "public",
+ "values": [
+ "none",
+ "cladded with “sufficient space to fill the wall”",
+ "cladded with “insufficient space to fill the wall”"
+ ]
+ },
+ "public.inspections_insulation_material": {
+ "name": "inspections_insulation_material",
+ "schema": "public",
+ "values": [
+ "empty 50-90",
+ "empty 100+",
+ "empty 30-40",
+ "empty less than 30",
+ "loose fibre/wool",
+ "eps/celo/king",
+ "fibre batts - with cavity",
+ "fibre batts - no cavity",
+ "loose bead",
+ "glued bead",
+ "formaldehyde",
+ "bubble wrap",
+ "poly chunks"
+ ]
+ },
+ "public.inspections_rendered": {
+ "name": "inspections_rendered",
+ "schema": "public",
+ "values": [
+ "no render",
+ "rendered with “insufficient” space between dpc and render",
+ "rendered with “sufficient” space between dpc and render"
+ ]
+ },
+ "public.inspections_roof_orientation": {
+ "name": "inspections_roof_orientation",
+ "schema": "public",
+ "values": [
+ "north",
+ "east",
+ "south",
+ "west",
+ "north-east",
+ "north-west",
+ "south-east",
+ "south-west",
+ "n/s split",
+ "e/w split",
+ "ne/sw split",
+ "nw/se split",
+ "flat roof",
+ "no roof",
+ "roof too small",
+ "already has solar pv"
+ ]
+ },
+ "public.inspections_tile_hung": {
+ "name": "inspections_tile_hung",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "first floor flats are tile hung"
+ ]
+ },
+ "public.inspections_wall_construction": {
+ "name": "inspections_wall_construction",
+ "schema": "public",
+ "values": [
+ "cavity",
+ "solid",
+ "system built",
+ "timber framed",
+ "steel framed",
+ "re-walled cavity",
+ "mansard pre-fab",
+ "mansard ewi",
+ "mansard re-walled"
+ ]
+ },
+ "public.inspections_wall_insulation": {
+ "name": "inspections_wall_insulation",
+ "schema": "public",
+ "values": [
+ "empty cavity",
+ "filled at build",
+ "partial",
+ "retro drilled",
+ "ewi",
+ "iwi",
+ "solid non-cavity",
+ "system built",
+ "timber framed",
+ "steel framed"
+ ]
+ },
+ "public.cost_unit": {
+ "name": "cost_unit",
+ "schema": "public",
+ "values": [
+ "gbp_sq_meter",
+ "gbp_per_unit",
+ "gbp_per_m2",
+ "gbp_per_m"
+ ]
+ },
+ "public.depth_unit": {
+ "name": "depth_unit",
+ "schema": "public",
+ "values": [
+ "mm"
+ ]
+ },
+ "public.type": {
+ "name": "type",
+ "schema": "public",
+ "values": [
+ "suspended_floor_insulation",
+ "solid_floor_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "cavity_wall_insulation",
+ "mechanical_ventilation",
+ "loft_insulation",
+ "exposed_floor_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "cavity_wall_extraction",
+ "iwi_wall_demolition",
+ "iwi_vapour_barrier",
+ "iwi_redecoration",
+ "suspended_floor_demolition",
+ "suspended_floor_redecoration",
+ "suspended_floor_vapour_barrier",
+ "solid_floor_demolition",
+ "solid_floor_preparation",
+ "solid_floor_vapour_barrier",
+ "solid_floor_redecoration",
+ "ewi_wall_demolition",
+ "ewi_wall_preparation",
+ "ewi_wall_redecoration",
+ "low_energy_lighting_installation",
+ "flat_roof_preparation",
+ "flat_roof_vapour_barrier",
+ "flat_roof_waterproofing",
+ "windows_glazing",
+ "secondary_glazing",
+ "double_glazing",
+ "trickle_vent",
+ "door_undercut",
+ "solar_pv",
+ "solar_battery",
+ "scaffolding",
+ "high_heat_retention_storage_heaters",
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "sealing_fireplace"
+ ]
+ },
+ "public.r_value_unit": {
+ "name": "r_value_unit",
+ "schema": "public",
+ "values": [
+ "square_meter_kelvin_per_watt"
+ ]
+ },
+ "public.size_unit": {
+ "name": "size_unit",
+ "schema": "public",
+ "values": [
+ "kWp",
+ "kW",
+ "watt",
+ "storey"
+ ]
+ },
+ "public.thermal_conductivity_unit": {
+ "name": "thermal_conductivity_unit",
+ "schema": "public",
+ "values": [
+ "watt_per_meter_kelvin"
+ ]
+ },
+ "public.goal": {
+ "name": "goal",
+ "schema": "public",
+ "values": [
+ "Valuation Improvement",
+ "Increasing EPC",
+ "Reducing CO2 emissions",
+ "Energy Savings",
+ "None"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "creator",
+ "admin",
+ "read",
+ "write"
+ ]
+ },
+ "public.status": {
+ "name": "status",
+ "schema": "public",
+ "values": [
+ "scoping",
+ "survey",
+ "assessment",
+ "tendering",
+ "project underway",
+ "completion; status: on track",
+ "completion; status: delayed",
+ "completion; status: at risk",
+ "completion; status: completed",
+ "needs review"
+ ]
+ },
+ "public.epc": {
+ "name": "epc",
+ "schema": "public",
+ "values": [
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G"
+ ]
+ },
+ "public.creation_status": {
+ "name": "creation_status",
+ "schema": "public",
+ "values": [
+ "LOADING",
+ "READY",
+ "ERROR"
+ ]
+ },
+ "public.housing_type": {
+ "name": "housing_type",
+ "schema": "public",
+ "values": [
+ "Private",
+ "Social"
+ ]
+ },
+ "public.measure_type": {
+ "name": "measure_type",
+ "schema": "public",
+ "values": [
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "high_heat_retention_storage_heaters",
+ "secondary_heating",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "cylinder_thermostat",
+ "cavity_wall_insulation",
+ "extension_cavity_wall_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "loft_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "solid_floor_insulation",
+ "suspended_floor_insulation",
+ "double_glazing",
+ "secondary_glazing",
+ "draught_proofing",
+ "mechanical_ventilation",
+ "low_energy_lighting",
+ "solar_pv",
+ "hot_water_tank_insulation",
+ "sealing_open_fireplace"
+ ]
+ },
+ "public.plan_type": {
+ "name": "plan_type",
+ "schema": "public",
+ "values": [
+ "solar_eco4",
+ "solar_hhrsh_eco4",
+ "empty_cavity_eco",
+ "partial_cavity_eco",
+ "extraction_eco"
+ ]
+ },
+ "public.unit_quantity": {
+ "name": "unit_quantity",
+ "schema": "public",
+ "values": [
+ "m2",
+ "part",
+ "kwp"
+ ]
+ },
+ "public.scenario_type": {
+ "name": "scenario_type",
+ "schema": "public",
+ "values": [
+ "unit",
+ "building"
+ ]
+ },
+ "public.source": {
+ "name": "source",
+ "schema": "public",
+ "values": [
+ "portfolio_id"
+ ]
+ },
+ "public.file_source": {
+ "name": "file_source",
+ "schema": "public",
+ "values": [
+ "pas hub",
+ "sharepoint",
+ "hubspot",
+ "ecmk"
+ ]
+ },
+ "public.file_type": {
+ "name": "file_type",
+ "schema": "public",
+ "values": [
+ "photo_pack",
+ "site_note",
+ "rd_sap_site_note",
+ "pas_2023_ventilation",
+ "pas_2023_condition",
+ "pas_significance",
+ "par_photo_pack",
+ "pas_2023_property",
+ "pas_2023_occupancy",
+ "ecmk_site_note",
+ "ecmk_rd_sap_site_note",
+ "ecmk_survey_xml"
+ ]
+ },
+ "public.user_profiles_property_count": {
+ "name": "user_profiles_property_count",
+ "schema": "public",
+ "values": [
+ "1",
+ "2–5",
+ "6–20",
+ "21+",
+ "1–50",
+ "51–100",
+ "101–300",
+ "301–1000",
+ "1000+"
+ ]
+ },
+ "public.user_profiles_referral_source": {
+ "name": "user_profiles_referral_source",
+ "schema": "public",
+ "values": [
+ "search",
+ "social_media",
+ "NRLA",
+ "partner",
+ "word_of_mouth",
+ "other"
+ ]
+ },
+ "public.user_profiles_user_type": {
+ "name": "user_profiles_user_type",
+ "schema": "public",
+ "values": [
+ "private_landlord",
+ "private_tenant",
+ "social_landlord",
+ "social_tenant",
+ "homeowner",
+ "other"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ 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 9436ff4e..e0511743 100644
--- a/src/app/db/migrations/meta/_journal.json
+++ b/src/app/db/migrations/meta/_journal.json
@@ -1191,6 +1191,13 @@
"when": 1776351792028,
"tag": "0169_freezing_moonstone",
"breakpoints": true
+ },
+ {
+ "idx": 170,
+ "version": "7",
+ "when": 1776357524564,
+ "tag": "0170_furry_moonstone",
+ "breakpoints": true
}
]
}
\ No newline at end of file
From e0449d01a24a198fb9bd7b0367f1e9ff22143a7f Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Thu, 16 Apr 2026 17:41:55 +0000
Subject: [PATCH 03/43] db migration
---
src/app/db/migrations/0171_chunky_wallow.sql | 2 +
src/app/db/migrations/meta/0171_snapshot.json | 6423 +++++++++++++++++
src/app/db/migrations/meta/_journal.json | 7 +
src/app/db/schema/bulk_address_uploads.ts | 19 +
4 files changed, 6451 insertions(+)
create mode 100644 src/app/db/migrations/0171_chunky_wallow.sql
create mode 100644 src/app/db/migrations/meta/0171_snapshot.json
create mode 100644 src/app/db/schema/bulk_address_uploads.ts
diff --git a/src/app/db/migrations/0171_chunky_wallow.sql b/src/app/db/migrations/0171_chunky_wallow.sql
new file mode 100644
index 00000000..b0904c3e
--- /dev/null
+++ b/src/app/db/migrations/0171_chunky_wallow.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "bulk_address_uploads" ADD COLUMN "source_headers" text[] DEFAULT '{}' NOT NULL;--> statement-breakpoint
+ALTER TABLE "bulk_address_uploads" ADD COLUMN "column_mapping" jsonb;
\ No newline at end of file
diff --git a/src/app/db/migrations/meta/0171_snapshot.json b/src/app/db/migrations/meta/0171_snapshot.json
new file mode 100644
index 00000000..4b5dee3b
--- /dev/null
+++ b/src/app/db/migrations/meta/0171_snapshot.json
@@ -0,0 +1,6423 @@
+{
+ "id": "cc0e7e0b-eadb-47e3-8dcc-53754d9e409e",
+ "prevId": "f5fcb7de-81c2-42a0-8775-abccb47a8216",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.postcode_search": {
+ "name": "postcode_search",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_data": {
+ "name": "result_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_updated_at": {
+ "name": "last_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postcode_search_postcode_unique": {
+ "name": "postcode_search_postcode_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "postcode"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bulk_address_uploads": {
+ "name": "bulk_address_uploads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_bucket": {
+ "name": "s3_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_key": {
+ "name": "s3_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filename": {
+ "name": "filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready_for_processing'"
+ },
+ "source_headers": {
+ "name": "source_headers",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "column_mapping": {
+ "name": "column_mapping",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.aspect_condition": {
+ "name": "aspect_condition",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "element_id": {
+ "name": "element_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_type": {
+ "name": "aspect_type",
+ "type": "aspect_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_instance": {
+ "name": "aspect_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quantity": {
+ "name": "quantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "install_date": {
+ "name": "install_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "renewal_year": {
+ "name": "renewal_year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "comments": {
+ "name": "comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "aspect_condition_element_id_element_id_fk": {
+ "name": "aspect_condition_element_id_element_id_fk",
+ "tableFrom": "aspect_condition",
+ "tableTo": "element",
+ "columnsFrom": [
+ "element_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.element": {
+ "name": "element",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "survey_id": {
+ "name": "survey_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_type": {
+ "name": "element_type",
+ "type": "element_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_instance": {
+ "name": "element_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "element_survey_id_property_condition_survey_id_fk": {
+ "name": "element_survey_id_property_condition_survey_id_fk",
+ "tableFrom": "element",
+ "tableTo": "property_condition_survey",
+ "columnsFrom": [
+ "survey_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_condition_survey": {
+ "name": "property_condition_survey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_company_data": {
+ "name": "hubspot_company_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "company_name": {
+ "name": "company_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_deal_data": {
+ "name": "hubspot_deal_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deal_id": {
+ "name": "deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dealname": {
+ "name": "dealname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dealstage": {
+ "name": "dealstage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "project_code": {
+ "name": "project_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "listing_id": {
+ "name": "listing_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome_notes": {
+ "name": "outcome_notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_description": {
+ "name": "major_condition_issue_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_photos": {
+ "name": "major_condition_issue_photos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_evidence_s3_url": {
+ "name": "major_condition_issue_evidence_s3_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_status": {
+ "name": "coordination_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_status": {
+ "name": "design_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pashub_link": {
+ "name": "pashub_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sharepoint_link": {
+ "name": "sharepoint_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dampmould_growth": {
+ "name": "dampmould_growth",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pre_sap": {
+ "name": "pre_sap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordinator": {
+ "name": "coordinator",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_completion_date": {
+ "name": "mtp_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_re_model_completion_date": {
+ "name": "mtp_re_model_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ioe_v3_completion_date": {
+ "name": "ioe_v3_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_measures": {
+ "name": "proposed_measures",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approved_package": {
+ "name": "approved_package",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "designer": {
+ "name": "designer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_type": {
+ "name": "design_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_completion_date": {
+ "name": "design_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actual_measures_installed": {
+ "name": "actual_measures_installed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer": {
+ "name": "installer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer_handover": {
+ "name": "installer_handover",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_status": {
+ "name": "lodgement_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measures_lodgement_date": {
+ "name": "measures_lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_commencement_date": {
+ "name": "expected_commencement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_comments": {
+ "name": "coordination_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyor": {
+ "name": "surveyor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "damp_mould_and_repairs_comments": {
+ "name": "damp_mould_and_repairs_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_date": {
+ "name": "confirmed_survey_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_time": {
+ "name": "confirmed_survey_time",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyed_date": {
+ "name": "surveyed_date",
+ "type": "timestamp (6) with time zone",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_status_tracker": {
+ "name": "property_status_tracker",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "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 (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": {
+ "property_status_tracker_property_id_property_id_fk": {
+ "name": "property_status_tracker_property_id_property_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_status_tracker_portfolio_id_portfolio_id_fk": {
+ "name": "property_status_tracker_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_store": {
+ "name": "epc_store",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api_created_at": {
+ "name": "epc_api_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api": {
+ "name": "epc_api",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_created_at": {
+ "name": "epc_page_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page": {
+ "name": "epc_page",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_rrn": {
+ "name": "epc_page_rrn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_epc_store_uprn": {
+ "name": "uq_epc_store_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.files_from_surveyor": {
+ "name": "files_from_surveyor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_json_url": {
+ "name": "s3_json_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "files_from_surveyor_portfolio_id_portfolio_id_fk": {
+ "name": "files_from_surveyor_portfolio_id_portfolio_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "files_from_surveyor_property_id_property_id_fk": {
+ "name": "files_from_surveyor_property_id_property_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package": {
+ "name": "funding_package",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheme": {
+ "name": "scheme",
+ "type": "scheme",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "project_funding": {
+ "name": "project_funding",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_uplift": {
+ "name": "total_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "full_project_score": {
+ "name": "full_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_plan_id_plan_id_fk": {
+ "name": "funding_package_plan_id_plan_id_fk",
+ "tableFrom": "funding_package",
+ "tableTo": "plan",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package_measures": {
+ "name": "funding_package_measures",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "funding_package_id": {
+ "name": "funding_package_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure": {
+ "name": "measure",
+ "type": "type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "material_id": {
+ "name": "material_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "innovation_uplift": {
+ "name": "innovation_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_measures_funding_package_id_funding_package_id_fk": {
+ "name": "funding_package_measures_funding_package_id_funding_package_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "funding_package",
+ "columnsFrom": [
+ "funding_package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "funding_package_measures_material_id_material_id_fk": {
+ "name": "funding_package_measures_material_id_material_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "material",
+ "columnsFrom": [
+ "material_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.inspections": {
+ "name": "inspections",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "archetype": {
+ "name": "archetype",
+ "type": "inspection_archetype",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archetype_2": {
+ "name": "archetype_2",
+ "type": "inspection_archetype_2",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_construction": {
+ "name": "wall_construction",
+ "type": "inspections_wall_construction",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation": {
+ "name": "insulation",
+ "type": "inspections_wall_insulation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation_material": {
+ "name": "insulation_material",
+ "type": "inspections_insulation_material",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "borescoped": {
+ "name": "borescoped",
+ "type": "inspection_borescoped",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_orientation": {
+ "name": "roof_orientation",
+ "type": "inspections_roof_orientation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tile_hung": {
+ "name": "tile_hung",
+ "type": "inspections_tile_hung",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rendered": {
+ "name": "rendered",
+ "type": "inspections_rendered",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cladding": {
+ "name": "cladding",
+ "type": "inspections_cladding",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_issues": {
+ "name": "access_issues",
+ "type": "inspections_access_issues",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "surveyor_name": {
+ "name": "surveyor_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "inspections_property_id_property_id_fk": {
+ "name": "inspections_property_id_property_id_fk",
+ "tableFrom": "inspections",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.material": {
+ "name": "material",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "type",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_unit": {
+ "name": "cost_unit",
+ "type": "cost_unit",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "innovation_rate": {
+ "name": "innovation_rate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "size": {
+ "name": "size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "size_unit": {
+ "name": "size_unit",
+ "type": "size_unit",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "includes_scaffolding": {
+ "name": "includes_scaffolding",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "includes_battery": {
+ "name": "includes_battery",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "battery_size": {
+ "name": "battery_size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organisation": {
+ "name": "organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "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()"
+ },
+ "hubspot_company_id": {
+ "name": "hubspot_company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.portfolio_organisation": {
+ "name": "portfolio_organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organisation_id": {
+ "name": "organisation_id",
+ "type": "uuid",
+ "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": {
+ "portfolio_organisation_portfolio_id_portfolio_id_fk": {
+ "name": "portfolio_organisation_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "portfolio_organisation_organisation_id_organisation_id_fk": {
+ "name": "portfolio_organisation_organisation_id_organisation_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "organisation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_organisation_portfolio_id_unique": {
+ "name": "portfolio_organisation_portfolio_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "building_reference_number": {
+ "name": "building_reference_number",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "installed_measures_sap_point_adjustment": {
+ "name": "installed_measures_sap_point_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_sap_points_adjusted_for_installed_measures": {
+ "name": "is_sap_points_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "original_sap_points": {
+ "name": "original_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_sap_points": {
+ "name": "lodged_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_epc_rating": {
+ "name": "lodged_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_portfolio_uprn": {
+ "name": "uq_property_portfolio_uprn",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"property\".\"uprn\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_expired": {
+ "name": "is_expired",
+ "type": "boolean",
+ "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
+ },
+ "sap_05_overwritten": {
+ "name": "sap_05_overwritten",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "sap_05_score": {
+ "name": "sap_05_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_05_epc_rating": {
+ "name": "sap_05_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": 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
+ },
+ "original_co2_emissions": {
+ "name": "original_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_primary_energy_consumption": {
+ "name": "original_primary_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand": {
+ "name": "original_current_energy_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand_heating_hotwater": {
+ "name": "original_current_energy_demand_heating_hotwater",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_co2_adjustment": {
+ "name": "installed_measures_co2_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_energy_demand_adjustment": {
+ "name": "installed_measures_energy_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_total_energy_bill_adjustment": {
+ "name": "installed_measures_total_energy_bill_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_heat_demand_adjustment": {
+ "name": "installed_measures_heat_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_epc_adjusted_for_installed_measures": {
+ "name": "is_epc_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "lodged_co2_emissions": {
+ "name": "lodged_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_heat_demand": {
+ "name": "lodged_heat_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_been_remodelled": {
+ "name": "has_been_remodelled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "environment_impact_current": {
+ "name": "environment_impact_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_details_epc_property_portfolio": {
+ "name": "uq_property_details_epc_property_portfolio",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "uq_property_details_spatial_uprn": {
+ "name": "uq_property_details_spatial_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.installed_measure": {
+ "name": "installed_measure",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_type": {
+ "name": "measure_type",
+ "type": "measure_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "installed_at": {
+ "name": "installed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "sap_points": {
+ "name": "sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "carbon_savings": {
+ "name": "carbon_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kwh_savings": {
+ "name": "kwh_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bill_savings": {
+ "name": "bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_demand_savings": {
+ "name": "heat_demand_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "idx_installed_measure_uprn": {
+ "name": "idx_installed_measure_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_active": {
+ "name": "idx_installed_measure_uprn_active",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_measure_type": {
+ "name": "idx_installed_measure_measure_type",
+ "columns": [
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_measure": {
+ "name": "idx_installed_measure_uprn_measure",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "post_sap_points": {
+ "name": "post_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_epc_rating": {
+ "name": "post_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_co2_emissions": {
+ "name": "post_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_savings": {
+ "name": "co2_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_bill": {
+ "name": "post_energy_bill",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_bill_savings": {
+ "name": "energy_bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_consumption": {
+ "name": "post_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_consumption_savings": {
+ "name": "energy_consumption_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_post_retrofit": {
+ "name": "valuation_post_retrofit",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_increase": {
+ "name": "valuation_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_of_works": {
+ "name": "cost_of_works",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contingency_cost": {
+ "name": "contingency_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plan_type": {
+ "name": "plan_type",
+ "type": "plan_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_plan_portfolio_scenario": {
+ "name": "idx_plan_portfolio_scenario",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_latest_per_property": {
+ "name": "idx_plan_latest_per_property",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "idx_plan_recommendations_plan_id": {
+ "name": "idx_plan_recommendations_plan_id",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_recommendations_plan_rec": {
+ "name": "idx_plan_recommendations_plan_rec",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "contingency_cost": {
+ "name": "contingency_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": {
+ "recommendation_property_id_idx": {
+ "name": "recommendation_property_id_idx",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_defaults": {
+ "name": "idx_recommendation_active_defaults",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_id_property": {
+ "name": "idx_recommendation_active_id_property",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": false
+ },
+ "quantity_unit": {
+ "name": "quantity_unit",
+ "type": "unit_quantity",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "recommendation_materials_recommendation_id_idx": {
+ "name": "recommendation_materials_recommendation_id_idx",
+ "columns": [
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": "cascade",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal_value": {
+ "name": "goal_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ashp_cop": {
+ "name": "ashp_cop",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2.8
+ },
+ "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
+ },
+ "contingency": {
+ "name": "contingency",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funding": {
+ "name": "funding",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sub_task": {
+ "name": "sub_task",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cloud_logs_url": {
+ "name": "cloud_logs_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sub_task_task_id_tasks_id_fk": {
+ "name": "sub_task_task_id_tasks_id_fk",
+ "tableFrom": "sub_task",
+ "tableTo": "tasks",
+ "columnsFrom": [
+ "task_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_source": {
+ "name": "task_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "service": {
+ "name": "service",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team": {
+ "name": "team",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "uuid",
+ "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": {
+ "team_org_id_organisation_id_fk": {
+ "name": "team_org_id_organisation_id_fk",
+ "tableFrom": "team",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "org_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_members": {
+ "name": "team_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "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": {
+ "team_members_user_id_user_id_fk": {
+ "name": "team_members_user_id_user_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_members_team_id_team_id_fk": {
+ "name": "team_members_team_id_team_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_portfolio_permissions": {
+ "name": "team_portfolio_permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "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": {
+ "team_portfolio_permissions_team_id_team_id_fk": {
+ "name": "team_portfolio_permissions_team_id_team_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_portfolio_permissions_portfolio_id_portfolio_id_fk": {
+ "name": "team_portfolio_permissions_portfolio_id_portfolio_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.uploaded_files": {
+ "name": "uploaded_files",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "s3_file_bucket": {
+ "name": "s3_file_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_file_key": {
+ "name": "s3_file_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_upload_timestamp": {
+ "name": "s3_upload_timestamp",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_listing_id": {
+ "name": "hubspot_listing_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_type": {
+ "name": "file_type",
+ "type": "file_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_source": {
+ "name": "file_source",
+ "type": "file_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_id": {
+ "name": "oauth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_provider": {
+ "name": "oauth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "onboarded": {
+ "name": "onboarded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "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": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_profiles": {
+ "name": "user_profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_type": {
+ "name": "user_type",
+ "type": "user_profiles_user_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_count": {
+ "name": "property_count",
+ "type": "user_profiles_property_count",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goals": {
+ "name": "goals",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referral_source": {
+ "name": "referral_source",
+ "type": "user_profiles_referral_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nrla_membership_id": {
+ "name": "nrla_membership_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accepted_privacy": {
+ "name": "accepted_privacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accepted_privacy_at": {
+ "name": "accepted_privacy_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "marketing_opt_in": {
+ "name": "marketing_opt_in",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "marketing_opt_in_at": {
+ "name": "marketing_opt_in_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_name": {
+ "name": "last_name",
+ "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": {
+ "user_profiles_user_id_user_id_fk": {
+ "name": "user_profiles_user_id_user_id_fk",
+ "tableFrom": "user_profiles",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verificationToken": {
+ "name": "verificationToken",
+ "schema": "",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": [
+ "identifier",
+ "token"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.whlg": {
+ "name": "whlg",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.aspect_type": {
+ "name": "aspect_type",
+ "schema": "public",
+ "values": [
+ "material",
+ "condition",
+ "type",
+ "area",
+ "configuration",
+ "presence",
+ "risk",
+ "severity",
+ "location",
+ "finish",
+ "insulation",
+ "pointing",
+ "spalling",
+ "lintels",
+ "cladding",
+ "category",
+ "quantity",
+ "adequacy",
+ "rating",
+ "strategy",
+ "extent",
+ "distribution",
+ "structure",
+ "covering",
+ "fire_rating",
+ "external_decoration",
+ "work_required",
+ "age_band",
+ "construction_type",
+ "classification",
+ "system"
+ ]
+ },
+ "public.element_type": {
+ "name": "element_type",
+ "schema": "public",
+ "values": [
+ "property",
+ "property_construction_type",
+ "property_classification",
+ "property_age_band",
+ "storey_count",
+ "floor_level",
+ "floor_level_front_door",
+ "accessible_housing_register",
+ "asbestos",
+ "quality_standard",
+ "ccu",
+ "passenger_lift",
+ "stairlift",
+ "disabled_hoist_tracking",
+ "disabled_facilities",
+ "steps_to_front_door",
+ "roof",
+ "pitched_roof_covering",
+ "flat_roof_covering",
+ "rainwater_goods",
+ "loft_insulation",
+ "porch_canopy",
+ "chimney",
+ "fascia",
+ "soffit",
+ "fascia_soffit_bargeboards",
+ "gutters",
+ "store_roof",
+ "garage_roof",
+ "garage_and_store_roof",
+ "external_wall",
+ "external_noise_insulation",
+ "primary_wall",
+ "secondary_wall",
+ "downpipes",
+ "external_decoration",
+ "cladding",
+ "spandrel_panels",
+ "garage_walls",
+ "party_wall_fire_break",
+ "external_brickwork_pointing",
+ "internal_downpipes_external_area",
+ "external_windows",
+ "communal_windows",
+ "secondary_glazing",
+ "store_windows",
+ "garage_windows",
+ "garage_and_store_windows",
+ "external_door",
+ "front_door",
+ "rear_door",
+ "store_door",
+ "garage_door",
+ "garage_and_store_door",
+ "communal_entrance_door",
+ "main_door",
+ "block_entrance_door",
+ "lintel",
+ "patio_french_door",
+ "door_entry_handset",
+ "paths_and_hardstandings",
+ "parking_areas",
+ "boundary_walls",
+ "front_fencing",
+ "rear_fencing",
+ "side_fencing",
+ "rear_gate",
+ "front_gate",
+ "gates",
+ "retaining_walls",
+ "private_balcony",
+ "balcony_balustrade",
+ "outbuildings",
+ "garage_structure",
+ "paving",
+ "roads",
+ "soil_and_vent",
+ "solar_thermals",
+ "drop_kerb",
+ "outbuilding_overhaul",
+ "external_structural_defects",
+ "access_ramp",
+ "kitchen",
+ "kitchen_space_layout",
+ "tenant_installed_kitchen",
+ "kitchen_extractor_fan",
+ "bathroom",
+ "secondary_bathroom",
+ "secondary_toilet",
+ "bathroom_extractor_fan",
+ "additional_wc_or_whb",
+ "bathroom_remaining_life_source",
+ "kitchen_remaining_life_source",
+ "central_heating",
+ "heating_boiler",
+ "heating_distribution",
+ "secondary_heating",
+ "hot_water_system",
+ "cold_water_storage",
+ "heating_system",
+ "boiler_fuel",
+ "water_heating",
+ "programmable_heating",
+ "community_heating",
+ "gas_available",
+ "heat_recovery_units",
+ "heating_improvements",
+ "electrical_wiring",
+ "consumer_unit",
+ "smoke_detection",
+ "heat_detection",
+ "carbon_monoxide_detection",
+ "fire_door_rating",
+ "fire_risk_assessment",
+ "internal_wiring",
+ "electrics",
+ "communal_heating",
+ "communal_boiler",
+ "communal_electrics",
+ "communal_fire_alarm",
+ "communal_emergency_lighting",
+ "communal_door_entry",
+ "communal_cctv",
+ "communal_bin_store",
+ "communal_bin_store_doors",
+ "communal_bin_store_walls",
+ "communal_bin_store_roof",
+ "communal_refuse_chute",
+ "communal_floor_covering",
+ "communal_kitchen",
+ "communal_bathroom",
+ "communal_toilets",
+ "communal_gates",
+ "communal_lift",
+ "communal_passenger_lift",
+ "communal_balcony_walkway",
+ "communal_entrance",
+ "communal_internal_decorations",
+ "communal_internal_floor",
+ "communal_walkways",
+ "communal_external_doors",
+ "communal_stairs",
+ "communal_aerial",
+ "communal_aov",
+ "communal_internal_doors",
+ "communal_lateral_mains",
+ "communal_lighting",
+ "communal_lighting_conductor",
+ "communal_store_roof",
+ "communal_store_walls",
+ "communal_store_doors",
+ "communal_warden_call_system",
+ "communal_bms",
+ "communal_booster_pump",
+ "communal_dry_riser",
+ "communal_wet_riser",
+ "communal_cold_water_storage",
+ "communal_sprinkler",
+ "communal_plug_sockets",
+ "communal_circulation_space",
+ "ffhh_damp",
+ "ffhh_hold_and_cold_water",
+ "ffhh_drainage_lavatories",
+ "ffhh_neglected",
+ "ffhh_natural_light",
+ "ffhh_ventilation",
+ "ffhh_food_prep_and_washup",
+ "ffhh_unsafe_layout",
+ "ffhh_unstable_building",
+ "hhsrs_damp_and_mould",
+ "hhsrs_excess_cold",
+ "hhsrs_excess_heat",
+ "hhsrs_asbestos_and_mmf",
+ "hhsrs_biocides",
+ "hhsrs_carbon_monoxide",
+ "hhsrs_lead",
+ "hhsrs_radiation",
+ "hhsrs_uncombusted_fuel_gas",
+ "hhsrs_volatile_organic_compounds",
+ "hhsrs_crowding_and_space",
+ "hhsrs_entry_by_intruders",
+ "hhsrs_lighting",
+ "hhsrs_noise",
+ "hhsrs_domestic_hygiene_pests_refuse",
+ "hhsrs_food_safety",
+ "hhsrs_personal_hygiene_sanitation",
+ "hhsrs_water_supply",
+ "hhsrs_falls_associated_with_baths",
+ "hhsrs_falls_on_level_surfaces",
+ "hhsrs_falls_on_stairs",
+ "hhsrs_falls_between_levels",
+ "hhsrs_electrical_hazards",
+ "hhsrs_fire",
+ "hhsrs_flames_hot_surfaces",
+ "hhsrs_collision_and_entrapment",
+ "hhsrs_collision_hazards_low_headroom",
+ "hhsrs_explosions",
+ "hhsrs_ergonomics",
+ "hhsrs_structural_collapse",
+ "hhsrs_amenities"
+ ]
+ },
+ "public.document_type": {
+ "name": "document_type",
+ "schema": "public",
+ "values": [
+ "EPR",
+ "Condition Report",
+ "Evidence Report",
+ "Summary Information",
+ "Floor Plan",
+ "Scenario Draft EPC",
+ "Scenario Site Notes"
+ ]
+ },
+ "public.scheme": {
+ "name": "scheme",
+ "schema": "public",
+ "values": [
+ "eco4",
+ "gbis",
+ "whlg",
+ "none"
+ ]
+ },
+ "public.inspection_archetype_2": {
+ "name": "inspection_archetype_2",
+ "schema": "public",
+ "values": [
+ "detached",
+ "mid-terrace",
+ "enclosed mid-terrace",
+ "end-terrace",
+ "enclosed end-terrace",
+ "semi-detached"
+ ]
+ },
+ "public.inspection_archetype": {
+ "name": "inspection_archetype",
+ "schema": "public",
+ "values": [
+ "Bungalow",
+ "Flat",
+ "Maisonette",
+ "House",
+ "non-domestic"
+ ]
+ },
+ "public.inspection_borescoped": {
+ "name": "inspection_borescoped",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "refused"
+ ]
+ },
+ "public.inspections_access_issues": {
+ "name": "inspections_access_issues",
+ "schema": "public",
+ "values": [
+ "see notes",
+ "damp issues",
+ "foliage on walls",
+ "bushes against wall",
+ "trees around/anove property",
+ "high rise block flats/maisonettes",
+ "conservatory",
+ "lean-to",
+ "garage",
+ "extension",
+ "decking",
+ "shed against wall"
+ ]
+ },
+ "public.inspections_cladding": {
+ "name": "inspections_cladding",
+ "schema": "public",
+ "values": [
+ "none",
+ "cladded with “sufficient space to fill the wall”",
+ "cladded with “insufficient space to fill the wall”"
+ ]
+ },
+ "public.inspections_insulation_material": {
+ "name": "inspections_insulation_material",
+ "schema": "public",
+ "values": [
+ "empty 50-90",
+ "empty 100+",
+ "empty 30-40",
+ "empty less than 30",
+ "loose fibre/wool",
+ "eps/celo/king",
+ "fibre batts - with cavity",
+ "fibre batts - no cavity",
+ "loose bead",
+ "glued bead",
+ "formaldehyde",
+ "bubble wrap",
+ "poly chunks"
+ ]
+ },
+ "public.inspections_rendered": {
+ "name": "inspections_rendered",
+ "schema": "public",
+ "values": [
+ "no render",
+ "rendered with “insufficient” space between dpc and render",
+ "rendered with “sufficient” space between dpc and render"
+ ]
+ },
+ "public.inspections_roof_orientation": {
+ "name": "inspections_roof_orientation",
+ "schema": "public",
+ "values": [
+ "north",
+ "east",
+ "south",
+ "west",
+ "north-east",
+ "north-west",
+ "south-east",
+ "south-west",
+ "n/s split",
+ "e/w split",
+ "ne/sw split",
+ "nw/se split",
+ "flat roof",
+ "no roof",
+ "roof too small",
+ "already has solar pv"
+ ]
+ },
+ "public.inspections_tile_hung": {
+ "name": "inspections_tile_hung",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "first floor flats are tile hung"
+ ]
+ },
+ "public.inspections_wall_construction": {
+ "name": "inspections_wall_construction",
+ "schema": "public",
+ "values": [
+ "cavity",
+ "solid",
+ "system built",
+ "timber framed",
+ "steel framed",
+ "re-walled cavity",
+ "mansard pre-fab",
+ "mansard ewi",
+ "mansard re-walled"
+ ]
+ },
+ "public.inspections_wall_insulation": {
+ "name": "inspections_wall_insulation",
+ "schema": "public",
+ "values": [
+ "empty cavity",
+ "filled at build",
+ "partial",
+ "retro drilled",
+ "ewi",
+ "iwi",
+ "solid non-cavity",
+ "system built",
+ "timber framed",
+ "steel framed"
+ ]
+ },
+ "public.cost_unit": {
+ "name": "cost_unit",
+ "schema": "public",
+ "values": [
+ "gbp_sq_meter",
+ "gbp_per_unit",
+ "gbp_per_m2",
+ "gbp_per_m"
+ ]
+ },
+ "public.depth_unit": {
+ "name": "depth_unit",
+ "schema": "public",
+ "values": [
+ "mm"
+ ]
+ },
+ "public.type": {
+ "name": "type",
+ "schema": "public",
+ "values": [
+ "suspended_floor_insulation",
+ "solid_floor_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "cavity_wall_insulation",
+ "mechanical_ventilation",
+ "loft_insulation",
+ "exposed_floor_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "cavity_wall_extraction",
+ "iwi_wall_demolition",
+ "iwi_vapour_barrier",
+ "iwi_redecoration",
+ "suspended_floor_demolition",
+ "suspended_floor_redecoration",
+ "suspended_floor_vapour_barrier",
+ "solid_floor_demolition",
+ "solid_floor_preparation",
+ "solid_floor_vapour_barrier",
+ "solid_floor_redecoration",
+ "ewi_wall_demolition",
+ "ewi_wall_preparation",
+ "ewi_wall_redecoration",
+ "low_energy_lighting_installation",
+ "flat_roof_preparation",
+ "flat_roof_vapour_barrier",
+ "flat_roof_waterproofing",
+ "windows_glazing",
+ "secondary_glazing",
+ "double_glazing",
+ "trickle_vent",
+ "door_undercut",
+ "solar_pv",
+ "solar_battery",
+ "scaffolding",
+ "high_heat_retention_storage_heaters",
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "sealing_fireplace"
+ ]
+ },
+ "public.r_value_unit": {
+ "name": "r_value_unit",
+ "schema": "public",
+ "values": [
+ "square_meter_kelvin_per_watt"
+ ]
+ },
+ "public.size_unit": {
+ "name": "size_unit",
+ "schema": "public",
+ "values": [
+ "kWp",
+ "kW",
+ "watt",
+ "storey"
+ ]
+ },
+ "public.thermal_conductivity_unit": {
+ "name": "thermal_conductivity_unit",
+ "schema": "public",
+ "values": [
+ "watt_per_meter_kelvin"
+ ]
+ },
+ "public.goal": {
+ "name": "goal",
+ "schema": "public",
+ "values": [
+ "Valuation Improvement",
+ "Increasing EPC",
+ "Reducing CO2 emissions",
+ "Energy Savings",
+ "None"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "creator",
+ "admin",
+ "read",
+ "write"
+ ]
+ },
+ "public.status": {
+ "name": "status",
+ "schema": "public",
+ "values": [
+ "scoping",
+ "survey",
+ "assessment",
+ "tendering",
+ "project underway",
+ "completion; status: on track",
+ "completion; status: delayed",
+ "completion; status: at risk",
+ "completion; status: completed",
+ "needs review"
+ ]
+ },
+ "public.epc": {
+ "name": "epc",
+ "schema": "public",
+ "values": [
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G"
+ ]
+ },
+ "public.creation_status": {
+ "name": "creation_status",
+ "schema": "public",
+ "values": [
+ "LOADING",
+ "READY",
+ "ERROR"
+ ]
+ },
+ "public.housing_type": {
+ "name": "housing_type",
+ "schema": "public",
+ "values": [
+ "Private",
+ "Social"
+ ]
+ },
+ "public.measure_type": {
+ "name": "measure_type",
+ "schema": "public",
+ "values": [
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "high_heat_retention_storage_heaters",
+ "secondary_heating",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "cylinder_thermostat",
+ "cavity_wall_insulation",
+ "extension_cavity_wall_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "loft_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "solid_floor_insulation",
+ "suspended_floor_insulation",
+ "double_glazing",
+ "secondary_glazing",
+ "draught_proofing",
+ "mechanical_ventilation",
+ "low_energy_lighting",
+ "solar_pv",
+ "hot_water_tank_insulation",
+ "sealing_open_fireplace"
+ ]
+ },
+ "public.plan_type": {
+ "name": "plan_type",
+ "schema": "public",
+ "values": [
+ "solar_eco4",
+ "solar_hhrsh_eco4",
+ "empty_cavity_eco",
+ "partial_cavity_eco",
+ "extraction_eco"
+ ]
+ },
+ "public.unit_quantity": {
+ "name": "unit_quantity",
+ "schema": "public",
+ "values": [
+ "m2",
+ "part",
+ "kwp"
+ ]
+ },
+ "public.scenario_type": {
+ "name": "scenario_type",
+ "schema": "public",
+ "values": [
+ "unit",
+ "building"
+ ]
+ },
+ "public.source": {
+ "name": "source",
+ "schema": "public",
+ "values": [
+ "portfolio_id"
+ ]
+ },
+ "public.file_source": {
+ "name": "file_source",
+ "schema": "public",
+ "values": [
+ "pas hub",
+ "sharepoint",
+ "hubspot",
+ "ecmk"
+ ]
+ },
+ "public.file_type": {
+ "name": "file_type",
+ "schema": "public",
+ "values": [
+ "photo_pack",
+ "site_note",
+ "rd_sap_site_note",
+ "pas_2023_ventilation",
+ "pas_2023_condition",
+ "pas_significance",
+ "par_photo_pack",
+ "pas_2023_property",
+ "pas_2023_occupancy",
+ "ecmk_site_note",
+ "ecmk_rd_sap_site_note",
+ "ecmk_survey_xml"
+ ]
+ },
+ "public.user_profiles_property_count": {
+ "name": "user_profiles_property_count",
+ "schema": "public",
+ "values": [
+ "1",
+ "2–5",
+ "6–20",
+ "21+",
+ "1–50",
+ "51–100",
+ "101–300",
+ "301–1000",
+ "1000+"
+ ]
+ },
+ "public.user_profiles_referral_source": {
+ "name": "user_profiles_referral_source",
+ "schema": "public",
+ "values": [
+ "search",
+ "social_media",
+ "NRLA",
+ "partner",
+ "word_of_mouth",
+ "other"
+ ]
+ },
+ "public.user_profiles_user_type": {
+ "name": "user_profiles_user_type",
+ "schema": "public",
+ "values": [
+ "private_landlord",
+ "private_tenant",
+ "social_landlord",
+ "social_tenant",
+ "homeowner",
+ "other"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ 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 e0511743..721ae1c1 100644
--- a/src/app/db/migrations/meta/_journal.json
+++ b/src/app/db/migrations/meta/_journal.json
@@ -1198,6 +1198,13 @@
"when": 1776357524564,
"tag": "0170_furry_moonstone",
"breakpoints": true
+ },
+ {
+ "idx": 171,
+ "version": "7",
+ "when": 1776361262258,
+ "tag": "0171_chunky_wallow",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/app/db/schema/bulk_address_uploads.ts b/src/app/db/schema/bulk_address_uploads.ts
new file mode 100644
index 00000000..082ca8be
--- /dev/null
+++ b/src/app/db/schema/bulk_address_uploads.ts
@@ -0,0 +1,19 @@
+import { pgTable, uuid, text, timestamp, jsonb } from "drizzle-orm/pg-core";
+import { sql } from "drizzle-orm";
+
+export const bulkAddressUploads = pgTable("bulk_address_uploads", {
+ id: uuid("id").defaultRandom().primaryKey(),
+ portfolioId: text("portfolio_id").notNull(),
+ userId: text("user_id").notNull(),
+ s3Bucket: text("s3_bucket").notNull(),
+ s3Key: text("s3_key").notNull(),
+ filename: text("filename").notNull(),
+ status: text("status").notNull().default("ready_for_processing"),
+ sourceHeaders: text("source_headers").array().notNull().default(sql`'{}'`),
+ columnMapping: jsonb("column_mapping").$type>(),
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
+ updatedAt: timestamp("updated_at", { withTimezone: true })
+ .notNull()
+ .defaultNow()
+ .$onUpdate(() => new Date()),
+});
From c0719aaee6e1763810dcdbd676caa22f8600172e Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Thu, 16 Apr 2026 17:47:53 +0000
Subject: [PATCH 04/43] added functionality to the column mapper
---
bulk-address-upload.md | 98 ++++
.../bulk-uploads/[uploadId]/route.ts | 50 ++
.../[portfolioId]/bulk-uploads/route.ts | 24 +
.../upload/bulk-addresses/confirm/route.ts | 50 ++
src/app/api/upload/bulk-addresses/route.ts | 36 ++
.../portfolio/BulkUploadComingSoonModal.tsx | 459 ++++++++++++++++++
.../map-columns/MapColumnsClient.tsx | 271 +++++++++++
.../[uploadId]/map-columns/page.tsx | 33 ++
.../bulk-upload/[uploadId]/page.tsx | 137 ++++++
.../[slug]/(portfolio)/bulk-upload/page.tsx | 143 ++++++
10 files changed, 1301 insertions(+)
create mode 100644 bulk-address-upload.md
create mode 100644 src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/route.ts
create mode 100644 src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts
create mode 100644 src/app/api/upload/bulk-addresses/confirm/route.ts
create mode 100644 src/app/api/upload/bulk-addresses/route.ts
create mode 100644 src/app/components/portfolio/BulkUploadComingSoonModal.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/MapColumnsClient.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/page.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx
diff --git a/bulk-address-upload.md b/bulk-address-upload.md
new file mode 100644
index 00000000..643a012e
--- /dev/null
+++ b/bulk-address-upload.md
@@ -0,0 +1,98 @@
+# Bulk Address Upload — Implementation Tracker
+
+## Overview
+
+Upload CSV/XLSX to S3 (browser-direct via XHR with progress bar) → confirm in DB → redirect to upload list.
+Portfolio shows all uploads ordered by date. User picks which to continue.
+
+---
+
+## DB Migration (manual — do this first)
+
+```sql
+CREATE TABLE bulk_address_uploads (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ portfolio_id TEXT NOT NULL,
+ user_id TEXT NOT NULL,
+ s3_bucket TEXT NOT NULL,
+ s3_key TEXT NOT NULL,
+ filename TEXT NOT NULL,
+ status TEXT NOT NULL DEFAULT 'ready_for_processing',
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
+);
+```
+
+Status values: `ready_for_processing` | `processing` | `complete` | `failed`
+
+- [ ] Migration applied to dev
+- [ ] Migration applied to staging
+- [ ] Migration applied to prod
+
+---
+
+## Tasks
+
+### 1. Drizzle Schema
+- [ ] Create `src/app/db/schema/bulk_address_uploads.ts`
+- [ ] Import + spread into `src/app/db/db.ts`
+
+### 2. Confirm API Route
+- [ ] Create `src/app/api/upload/bulk-addresses/confirm/route.ts`
+ - POST `{ fileKey, filename, portfolioId, userId }`
+ - Inserts into `bulk_address_uploads`, `s3Bucket` from `RETROFIT_PLAN_INPUT_BUCKET_NAME` env
+ - Returns `{ id, s3Key, s3Bucket, status }`
+
+### 3. List API Route
+- [ ] Create `src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts`
+ - GET → all uploads for portfolio ordered by `created_at DESC`
+ - Returns array of upload records
+
+### 4. Modal — XHR Upload + Progress + Redirect to List
+- [ ] Replace `fetch` PUT → `XMLHttpRequest` in `handleUpload`
+- [ ] Add `progress: number | null` state
+- [ ] Show progress bar in dropzone while uploading
+- [ ] After XHR load: POST confirm → `router.push(/portfolio/[id]/bulk-upload)`
+
+### 5. Upload List Page
+- [ ] Create `src/app/portfolio/[portfolioId]/bulk-upload/page.tsx`
+ - Server component
+ - List all uploads: filename, status badge, created date, "Continue →" link
+ - Empty state if none
+ - Each row links to `/bulk-upload/[uploadId]`
+
+### 6. Upload Detail Page
+- [ ] Create `src/app/portfolio/[portfolioId]/bulk-upload/[uploadId]/page.tsx`
+ - Server component
+ - Shows: filename, `s3://bucket/key`, status, created date
+ - For now: "Your file is queued for processing"
+
+---
+
+## Flow
+
+```
+User drops/clicks file
+ → validate (size, extension, headers)
+ → GET presigned URL (/api/upload/bulk-addresses)
+ → XHR PUT to S3 (progress bar shown)
+ → POST confirm (/api/upload/bulk-addresses/confirm)
+ → redirect to list (/portfolio/[id]/bulk-upload)
+ → list page (all uploads, status badges, click to continue)
+ → detail page (/portfolio/[id]/bulk-upload/[uploadId])
+ → shows s3_uri + status
+```
+
+---
+
+## Files Touched
+
+| File | Status |
+|------|--------|
+| `src/app/db/schema/bulk_address_uploads.ts` | not started |
+| `src/app/db/db.ts` | not started |
+| `src/app/api/upload/bulk-addresses/confirm/route.ts` | not started |
+| `src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts` | not started |
+| `src/app/components/portfolio/BulkUploadComingSoonModal.tsx` | not started |
+| `src/app/portfolio/[portfolioId]/bulk-upload/page.tsx` | not started |
+| `src/app/portfolio/[portfolioId]/bulk-upload/[uploadId]/page.tsx` | not started |
diff --git a/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/route.ts b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/route.ts
new file mode 100644
index 00000000..520e9333
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/route.ts
@@ -0,0 +1,50 @@
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { eq } from "drizzle-orm";
+import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
+
+const PatchSchema = z.object({
+ columnMapping: z.record(z.string(), z.string()),
+});
+
+export async function PATCH(
+ request: NextRequest,
+ { params }: { params: { portfolioId: string; uploadId: string } }
+) {
+ const { uploadId } = params;
+
+ let body;
+ try {
+ body = PatchSchema.parse(await request.json());
+ } catch {
+ return NextResponse.json({ msg: "Invalid input" }, { status: 400 });
+ }
+
+ const values = Object.values(body.columnMapping);
+ const hasAddress = values.includes("address_1");
+ const hasPostcode = values.includes("postcode");
+ if (!hasAddress || !hasPostcode) {
+ return NextResponse.json(
+ { msg: "Mapping must include address_1 and postcode." },
+ { status: 422 }
+ );
+ }
+
+ try {
+ const [updated] = await db
+ .update(bulkAddressUploads)
+ .set({ columnMapping: body.columnMapping, status: "mapping_complete" })
+ .where(eq(bulkAddressUploads.id, uploadId))
+ .returning();
+
+ if (!updated) {
+ return NextResponse.json({ msg: "Not found" }, { status: 404 });
+ }
+
+ return NextResponse.json(updated, { status: 200 });
+ } catch (error) {
+ console.error("Failed to save column mapping:", error);
+ return NextResponse.json({ msg: "Internal server error" }, { status: 500 });
+ }
+}
diff --git a/src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts b/src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts
new file mode 100644
index 00000000..902de6d1
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/bulk-uploads/route.ts
@@ -0,0 +1,24 @@
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { eq, desc } from "drizzle-orm";
+import { NextRequest, NextResponse } from "next/server";
+
+export async function GET(
+ _request: NextRequest,
+ { params }: { params: { portfolioId: string } }
+) {
+ const { portfolioId } = params;
+
+ try {
+ const uploads = await db
+ .select()
+ .from(bulkAddressUploads)
+ .where(eq(bulkAddressUploads.portfolioId, portfolioId))
+ .orderBy(desc(bulkAddressUploads.createdAt));
+
+ return NextResponse.json(uploads, { status: 200 });
+ } catch (error) {
+ console.error("Failed to fetch bulk uploads:", error);
+ return NextResponse.json({ msg: "Internal server error" }, { status: 500 });
+ }
+}
diff --git a/src/app/api/upload/bulk-addresses/confirm/route.ts b/src/app/api/upload/bulk-addresses/confirm/route.ts
new file mode 100644
index 00000000..edc63571
--- /dev/null
+++ b/src/app/api/upload/bulk-addresses/confirm/route.ts
@@ -0,0 +1,50 @@
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
+
+const BodySchema = z.object({
+ fileKey: z.string(),
+ filename: z.string(),
+ portfolioId: z.string(),
+ userId: z.string(),
+ sourceHeaders: z.array(z.string()).default([]),
+});
+
+export async function POST(request: NextRequest) {
+ let body;
+ try {
+ body = BodySchema.parse(await request.json());
+ } catch (error) {
+ console.error("Invalid input:", error);
+ return NextResponse.json({ msg: "Invalid input" }, { status: 400 });
+ }
+
+ const bucket = process.env.RETROFIT_PLAN_INPUT_BUCKET_NAME;
+ if (!bucket) {
+ console.error("RETROFIT_PLAN_INPUT_BUCKET_NAME not set");
+ return NextResponse.json({ msg: "Server misconfiguration" }, { status: 500 });
+ }
+
+ try {
+ const [record] = await db
+ .insert(bulkAddressUploads)
+ .values({
+ portfolioId: body.portfolioId,
+ userId: body.userId,
+ s3Bucket: bucket,
+ s3Key: body.fileKey,
+ filename: body.filename,
+ sourceHeaders: body.sourceHeaders,
+ })
+ .returning();
+
+ return NextResponse.json(
+ { id: record.id, s3Key: record.s3Key, s3Bucket: record.s3Bucket, status: record.status },
+ { status: 201 }
+ );
+ } catch (error) {
+ console.error("Failed to record upload:", error);
+ return NextResponse.json({ msg: "Internal server error" }, { status: 500 });
+ }
+}
diff --git a/src/app/api/upload/bulk-addresses/route.ts b/src/app/api/upload/bulk-addresses/route.ts
new file mode 100644
index 00000000..9eb1c417
--- /dev/null
+++ b/src/app/api/upload/bulk-addresses/route.ts
@@ -0,0 +1,36 @@
+import { createS3Client } from "@/app/utils/s3";
+import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
+
+const BodySchema = z.object({
+ userId: z.string(),
+ portfolioId: z.string(),
+ fileKey: z.string(),
+ contentType: z.string(),
+});
+
+export async function POST(request: NextRequest) {
+ let body;
+ try {
+ body = BodySchema.parse(await request.json());
+ } catch (error) {
+ console.error("Invalid input:", error);
+ return NextResponse.json({ msg: "Invalid input" }, { status: 400 });
+ }
+
+ try {
+ const s3 = createS3Client();
+
+ const preSignedUrl = await s3.getSignedUrlPromise("putObject", {
+ Bucket: process.env.RETROFIT_PLAN_INPUT_BUCKET_NAME,
+ Key: body.fileKey,
+ ContentType: body.contentType,
+ Expires: 5 * 60,
+ });
+
+ return NextResponse.json({ url: preSignedUrl }, { status: 200 });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({ msg: "Internal server error" }, { status: 500 });
+ }
+}
diff --git a/src/app/components/portfolio/BulkUploadComingSoonModal.tsx b/src/app/components/portfolio/BulkUploadComingSoonModal.tsx
new file mode 100644
index 00000000..1cd47bfe
--- /dev/null
+++ b/src/app/components/portfolio/BulkUploadComingSoonModal.tsx
@@ -0,0 +1,459 @@
+"use client";
+
+import {
+ Dialog,
+ DialogBackdrop,
+ DialogPanel,
+ DialogTitle,
+ Transition,
+ TransitionChild,
+} from "@headlessui/react";
+import { Fragment, useRef, useState, DragEvent } from "react";
+import * as XLSX from "xlsx";
+import {
+ XMarkIcon,
+ DocumentTextIcon,
+ ArrowDownTrayIcon,
+ CloudArrowUpIcon,
+ InformationCircleIcon,
+ ArrowRightIcon,
+ CheckCircleIcon,
+ ExclamationCircleIcon,
+} from "@heroicons/react/24/outline";
+import { useSession } from "next-auth/react";
+import { useRouter } from "next/navigation";
+
+const MAX_FILE_SIZE_MB = 50;
+const ALLOWED_EXTENSIONS = [".csv", ".xlsx", ".xls"];
+const CONTENT_TYPES: Record = {
+ ".csv": "text/csv",
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+ ".xls": "application/vnd.ms-excel",
+};
+
+interface BulkUploadComingSoonModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ portfolioId: string;
+}
+
+function downloadTemplate() {
+ const ws = XLSX.utils.aoa_to_sheet([["Internal Reference (Optional)", "Address", "Postcode"]]);
+ const wb = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(wb, ws, "Properties");
+ XLSX.writeFile(wb, "bulk_upload_template.xlsx");
+}
+
+function getFileExtension(filename: string): string {
+ return filename.slice(filename.lastIndexOf(".")).toLowerCase();
+}
+
+function generateS3Key(userId: string, portfolioId: string, ext: string): string {
+ const timestamp = new Date().toISOString().replace(/[:.-]/g, "");
+ return `bulk-addresses/${userId}/${portfolioId}/${timestamp}/addresses${ext}`;
+}
+
+function validateFile(file: File): string | null {
+ const sizeMB = file.size / (1024 * 1024);
+ if (sizeMB > MAX_FILE_SIZE_MB) {
+ return `File too large. Max ${MAX_FILE_SIZE_MB}MB.`;
+ }
+ const ext = getFileExtension(file.name);
+ if (!ALLOWED_EXTENSIONS.includes(ext)) {
+ return "Only CSV or Excel files allowed.";
+ }
+ return null;
+}
+
+async function validateHeaders(file: File): Promise<{ error: string | null; headers: string[] }> {
+ const ext = getFileExtension(file.name);
+ let headers: string[] = [];
+
+ if (ext === ".csv") {
+ const text = await file.text();
+ const firstLine = text.split(/\r?\n/)[0] ?? "";
+ headers = firstLine.split(",").map((h) => h.trim().replace(/^["']|["']$/g, ""));
+ } else {
+ const buffer = await file.arrayBuffer();
+ const wb = XLSX.read(buffer, { sheetRows: 1 });
+ const sheet = wb.Sheets[wb.SheetNames[0]];
+ const rows = XLSX.utils.sheet_to_json(sheet, { header: 1 });
+ headers = ((rows[0] as string[]) ?? []).map((h) => String(h ?? "").trim());
+ }
+
+ const normalised = headers.map((h) => h.toLowerCase());
+ const hasAddress = normalised.some((h) => h.startsWith("address"));
+ const hasPostcode = normalised.some((h) => h === "postcode");
+
+ if (!hasAddress && !hasPostcode) {
+ return { error: "Missing required columns: Address and Postcode.", headers };
+ }
+ if (!hasAddress) {
+ return { error: "Missing required column: Address (or Address 1, Address 2, etc.).", headers };
+ }
+ if (!hasPostcode) {
+ return { error: 'Missing required column: "Postcode".', headers };
+ }
+ return { error: null, headers };
+}
+
+async function getPresignedUrl(
+ userId: string,
+ portfolioId: string,
+ fileKey: string,
+ contentType: string
+): Promise {
+ const res = await fetch("/api/upload/bulk-addresses", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ userId, portfolioId, fileKey, contentType }),
+ });
+ if (!res.ok) throw new Error("Failed to generate upload URL.");
+ const data = await res.json();
+ return data.url;
+}
+
+export default function BulkUploadComingSoonModal({
+ isOpen,
+ onClose,
+ portfolioId,
+}: BulkUploadComingSoonModalProps) {
+ const session = useSession();
+ const router = useRouter();
+ const fileInputRef = useRef(null);
+
+ const [isDragging, setIsDragging] = useState(false);
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [sourceHeaders, setSourceHeaders] = useState([]);
+ const [validationError, setValidationError] = useState(null);
+ const [validating, setValidating] = useState(false);
+ const [uploading, setUploading] = useState(false);
+ const [uploadProgress, setUploadProgress] = useState(null);
+ const [uploadError, setUploadError] = useState(null);
+
+ async function handleFile(file: File) {
+ setUploadError(null);
+ setSelectedFile(null);
+ setValidationError(null);
+
+ const sizeOrTypeError = validateFile(file);
+ if (sizeOrTypeError) {
+ setValidationError(sizeOrTypeError);
+ return;
+ }
+
+ setValidating(true);
+ const { error: headerError, headers } = await validateHeaders(file);
+ setValidating(false);
+
+ if (headerError) {
+ setValidationError(headerError);
+ return;
+ }
+
+ setSourceHeaders(headers);
+ setSelectedFile(file);
+ }
+
+ function handleDragOver(e: DragEvent) {
+ e.preventDefault();
+ setIsDragging(true);
+ }
+
+ function handleDragLeave() {
+ setIsDragging(false);
+ }
+
+ function handleDrop(e: DragEvent) {
+ e.preventDefault();
+ setIsDragging(false);
+ const file = e.dataTransfer.files[0];
+ if (file) handleFile(file);
+ }
+
+ function handleInputChange(e: React.ChangeEvent) {
+ const file = e.target.files?.[0];
+ if (file) handleFile(file);
+ }
+
+ function handleClose() {
+ setSelectedFile(null);
+ setSourceHeaders([]);
+ setValidationError(null);
+ setValidating(false);
+ setUploadError(null);
+ setUploading(false);
+ setUploadProgress(null);
+ onClose();
+ }
+
+ async function handleUpload() {
+ const userId = String(session.data?.user?.dbId ?? "");
+ if (!selectedFile || !userId) return;
+
+ setUploading(true);
+ setUploadProgress(0);
+ setUploadError(null);
+
+ try {
+ const ext = getFileExtension(selectedFile.name);
+ const contentType = CONTENT_TYPES[ext] ?? "application/octet-stream";
+ const fileKey = generateS3Key(userId, portfolioId, ext);
+
+ const presignedUrl = await getPresignedUrl(userId, portfolioId, fileKey, contentType);
+
+ await new Promise((resolve, reject) => {
+ const xhr = new XMLHttpRequest();
+ xhr.open("PUT", presignedUrl);
+ xhr.setRequestHeader("Content-Type", contentType);
+ xhr.upload.addEventListener("progress", (e) => {
+ if (e.lengthComputable) {
+ setUploadProgress(Math.round((e.loaded / e.total) * 100));
+ }
+ });
+ xhr.onload = () => {
+ if (xhr.status >= 200 && xhr.status < 300) resolve();
+ else reject(new Error(`S3 upload failed: ${xhr.status}`));
+ };
+ xhr.onerror = () => reject(new Error("Network error during upload"));
+ xhr.send(selectedFile);
+ });
+
+ const confirmRes = await fetch("/api/upload/bulk-addresses/confirm", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ fileKey, filename: selectedFile.name, portfolioId, userId, sourceHeaders }),
+ });
+ if (!confirmRes.ok) throw new Error("Failed to record upload.");
+
+ const { id: uploadId } = await confirmRes.json();
+ router.push(`/portfolio/${portfolioId}/bulk-upload/${uploadId}/map-columns`);
+ onClose();
+ } catch (err) {
+ setUploadError("Upload failed. Please try again, or contact a Domna representative if the issue persists.");
+ } finally {
+ setUploading(false);
+ setUploadProgress(null);
+ }
+ }
+
+ const canUpload = !!selectedFile && !uploading && !validating;
+
+ return (
+
+
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/MapColumnsClient.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/MapColumnsClient.tsx
new file mode 100644
index 00000000..19152820
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/MapColumnsClient.tsx
@@ -0,0 +1,271 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import Link from "next/link";
+import {
+ ArrowLeftIcon,
+ ArrowRightIcon,
+ TableCellsIcon,
+ ArrowsRightLeftIcon,
+} from "@heroicons/react/24/outline";
+
+const INTERNAL_FIELDS = [
+ { value: "address_1", label: "Address 1", required: true },
+ { value: "address_2", label: "Address 2", required: false },
+ { value: "address_3", label: "Address 3", required: false },
+ { value: "postcode", label: "Postcode", required: true },
+ { value: "internal_reference", label: "Internal Reference (Optional)", required: false },
+ { value: "skip", label: "Skip this column", required: false },
+];
+
+const REQUIRED_VALUES = ["address_1", "postcode"];
+
+function autoDetect(header: string): string {
+ const h = header.toLowerCase().replace(/[\s_\-]/g, "");
+ if (/^(address|addr)(line)?(1|one)?$/.test(h)) return "address_1";
+ if (/^(address|addr)(line)?(2|two)|^street$/.test(h)) return "address_2";
+ if (/^(address|addr)(line)?(3|three)|^locality$|^town$|^city$/.test(h)) return "address_3";
+ if (/^post(al)?code$|^postcode$|^pcode$/.test(h)) return "postcode";
+ if (/^(internal)?ref(erence)?$|^id$/.test(h)) return "internal_reference";
+ return "skip";
+}
+
+function buildInitialMapping(
+ headers: string[],
+ existing?: Record
+): Record {
+ const mapping: Record = {};
+ for (const h of headers) {
+ mapping[h] = existing?.[h] ?? autoDetect(h);
+ }
+ return mapping;
+}
+
+interface Props {
+ portfolioId: string;
+ uploadId: string;
+ filename: string;
+ sourceHeaders: string[];
+ existingMapping?: Record;
+}
+
+export default function MapColumnsClient({
+ portfolioId,
+ uploadId,
+ filename,
+ sourceHeaders,
+ existingMapping,
+}: Props) {
+ const router = useRouter();
+ const [mapping, setMapping] = useState>(
+ buildInitialMapping(sourceHeaders, existingMapping)
+ );
+ const [submitting, setSubmitting] = useState(false);
+ const [error, setError] = useState(null);
+
+ const mappedValues = Object.values(mapping).filter((v) => v !== "skip");
+ const missingRequired = REQUIRED_VALUES.filter((r) => !mappedValues.includes(r));
+ const canSubmit = missingRequired.length === 0 && !submitting;
+
+ function setField(header: string, value: string) {
+ setMapping((prev) => ({ ...prev, [header]: value }));
+ }
+
+ async function handleSubmit() {
+ if (!canSubmit) return;
+ setSubmitting(true);
+ setError(null);
+
+ try {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}`,
+ {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ columnMapping: mapping }),
+ }
+ );
+
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}));
+ throw new Error(data.msg ?? "Failed to save mapping.");
+ }
+
+ router.push(`/portfolio/${portfolioId}/bulk-upload/${uploadId}`);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "Something went wrong.");
+ setSubmitting(false);
+ }
+ }
+
+ return (
+
+ {/* Breadcrumb + step */}
+
+
+ Bulk Uploads › Column Remapper
+
+
+
+ Step 2 of 3
+
+
+ {[1, 2, 3].map((s) => (
+
+ ))}
+
+
+
+
+ {/* Header */}
+
+
+ Column Remapper
+
+
+ Align your spreadsheet headers with our internal property data structure to
+ ensure accurate address processing.
+
+
+
+ {/* Table */}
+
+ {/* Column headers */}
+
+
+ Spreadsheet Header
+
+
+
+ Internal Field Mapping
+
+
+ Status
+
+
+
+ {sourceHeaders.length === 0 ? (
+
+ No headers found in this file.
+
+ ) : (
+
+ {sourceHeaders.map((header) => {
+ const value = mapping[header] ?? "skip";
+ const isMapped = value !== "skip";
+ return (
+
+ {/* Source header */}
+
+
+
+
{header}
+
Source column
+
+
+
+ {/* Arrow */}
+
+
+ {/* Dropdown */}
+
+
+
+
+ {/* Status badge */}
+
+
+
+ {isMapped ? "Mapped" : "Skipped"}
+
+
+
+ );
+ })}
+
+ )}
+
+
+ {/* Validation error */}
+ {missingRequired.length > 0 && (
+
+ Required fields not yet mapped:{" "}
+ {missingRequired
+ .map((r) => INTERNAL_FIELDS.find((f) => f.value === r)?.label)
+ .join(", ")}
+
+ )}
+ {error &&
{error}
}
+
+ {/* Footer */}
+
+
+
+ Back
+
+
+
+
+ Cancel
+
+
+
+
+
+ {/* Pro tip */}
+
+
+ Pro Tip
+
+
+ “Ensure your source file doesn't have blank headers. Any column mapped to
+ “Skip” will be ignored during import.”
+
+
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/page.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/page.tsx
new file mode 100644
index 00000000..3ce0ac62
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/map-columns/page.tsx
@@ -0,0 +1,33 @@
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { eq } from "drizzle-orm";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+import { redirect, notFound } from "next/navigation";
+import MapColumnsClient from "./MapColumnsClient";
+
+export default async function MapColumnsPage(props: {
+ params: Promise<{ slug: string; uploadId: string }>;
+}) {
+ const { slug, uploadId } = await props.params;
+ const session = await getServerSession(AuthOptions);
+ if (!session) redirect("/login");
+
+ const [upload] = await db
+ .select()
+ .from(bulkAddressUploads)
+ .where(eq(bulkAddressUploads.id, uploadId))
+ .limit(1);
+
+ if (!upload) notFound();
+
+ return (
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx
new file mode 100644
index 00000000..3955a571
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/page.tsx
@@ -0,0 +1,137 @@
+"use server";
+
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { eq } from "drizzle-orm";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+import { redirect, notFound } from "next/navigation";
+import Link from "next/link";
+import {
+ ArrowLeftIcon,
+ ArrowRightIcon,
+ CheckCircleIcon,
+ ClockIcon,
+ ExclamationCircleIcon,
+ ArrowPathIcon,
+} from "@heroicons/react/24/outline";
+
+function formatDate(date: Date) {
+ return new Intl.DateTimeFormat("en-GB", {
+ day: "2-digit",
+ month: "short",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ }).format(date);
+}
+
+const STATUS_CONFIG = {
+ ready_for_processing: {
+ icon: ClockIcon,
+ iconBg: "bg-amber-50",
+ iconColor: "text-amber-500",
+ title: "Awaiting column mapping",
+ body: "Map your spreadsheet columns to our internal fields before processing can begin.",
+ cta: true,
+ },
+ mapping_complete: {
+ icon: CheckCircleIcon,
+ iconBg: "bg-blue-50",
+ iconColor: "text-blue-500",
+ title: "Queued for processing",
+ body: "Column mapping saved. Your file is queued and will be processed shortly.",
+ cta: false,
+ },
+ processing: {
+ icon: ArrowPathIcon,
+ iconBg: "bg-blue-50",
+ iconColor: "text-blue-500",
+ title: "Processing…",
+ body: "Your file is currently being processed. This may take a few minutes.",
+ cta: false,
+ },
+ complete: {
+ icon: CheckCircleIcon,
+ iconBg: "bg-green-50",
+ iconColor: "text-green-500",
+ title: "Processing complete",
+ body: "All addresses have been imported into your portfolio.",
+ cta: false,
+ },
+ failed: {
+ icon: ExclamationCircleIcon,
+ iconBg: "bg-red-50",
+ iconColor: "text-red-500",
+ title: "Processing failed",
+ body: "Something went wrong during processing. Contact a Domna representative for assistance.",
+ cta: false,
+ },
+} as const;
+
+export default async function BulkUploadDetailPage(props: {
+ params: Promise<{ slug: string; uploadId: string }>;
+}) {
+ const { slug, uploadId } = await props.params;
+ const session = await getServerSession(AuthOptions);
+ if (!session) redirect("/login");
+
+ const [upload] = await db
+ .select()
+ .from(bulkAddressUploads)
+ .where(eq(bulkAddressUploads.id, uploadId))
+ .limit(1);
+
+ if (!upload) notFound();
+
+ const statusKey = upload.status as keyof typeof STATUS_CONFIG;
+ const config = STATUS_CONFIG[statusKey] ?? STATUS_CONFIG.ready_for_processing;
+ const Icon = config.icon;
+
+ return (
+
+ {/* Back */}
+
+
+ Back to uploads
+
+
+ {/* Header */}
+
+
+ Bulk Upload
+
+
+ {upload.filename}
+
+
Uploaded {formatDate(upload.createdAt)}
+
+
+ {/* Status card */}
+
+
+
+
+
+
+
{config.title}
+
{config.body}
+ {config.cta && (
+
+ Map Columns
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx
new file mode 100644
index 00000000..cc5a9496
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/page.tsx
@@ -0,0 +1,143 @@
+"use server";
+
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { eq, desc } from "drizzle-orm";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+import { redirect } from "next/navigation";
+import Link from "next/link";
+import {
+ ArrowRightIcon,
+ DocumentTextIcon,
+ CloudArrowUpIcon,
+} from "@heroicons/react/24/outline";
+
+const STATUS_LABELS: Record = {
+ ready_for_processing: { label: "Ready", classes: "bg-amber-100 text-amber-700" },
+ processing: { label: "Processing", classes: "bg-blue-100 text-blue-700" },
+ complete: { label: "Complete", classes: "bg-green-100 text-green-700" },
+ failed: { label: "Failed", classes: "bg-red-100 text-red-700" },
+};
+
+function formatDate(date: Date) {
+ return new Intl.DateTimeFormat("en-GB", {
+ day: "2-digit",
+ month: "short",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ }).format(date);
+}
+
+export default async function BulkUploadListPage(props: {
+ params: Promise<{ slug: string }>;
+}) {
+ const { slug } = await props.params;
+ const session = await getServerSession(AuthOptions);
+ if (!session) redirect("/login");
+
+ const uploads = await db
+ .select()
+ .from(bulkAddressUploads)
+ .where(eq(bulkAddressUploads.portfolioId, slug))
+ .orderBy(desc(bulkAddressUploads.createdAt));
+
+ return (
+
+ {/* Header */}
+
+
+ Portfolio › Bulk Uploads
+
+
+ Batch Uploads
+
+
+ Select an upload to continue processing, or start a new import.
+
+
+
+ {uploads.length === 0 ? (
+ /* Empty state */
+
+
+
+
+
No uploads yet
+
+ Use the Bulk Upload button on your portfolio to get started.
+
+
+ ) : (
+ /* Upload list */
+
+ {/* Column headers */}
+
+
+ File
+
+
+ Uploaded
+
+
+ Status
+
+
+
+
+ {uploads.map((upload) => {
+ const status = STATUS_LABELS[upload.status] ?? {
+ label: upload.status,
+ classes: "bg-gray-100 text-gray-600",
+ };
+ return (
+
+ {/* Filename */}
+
+
+
+
+
+
+ {upload.filename}
+
+
+ {upload.s3Key.split("/").pop()}
+
+
+
+
+ {/* Date */}
+
+
+ {formatDate(upload.createdAt)}
+
+
+
+ {/* Status badge */}
+
+
+
+ {status.label}
+
+
+
+ {/* Arrow */}
+
+
+ );
+ })}
+
+ )}
+
+ );
+}
From 6e8cd56159f7ab7981470eaf269e5a03735559cf Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Thu, 16 Apr 2026 21:12:12 +0000
Subject: [PATCH 05/43] initial set up for installer and client input
capabilities
---
src/app/db/migrations/meta/_journal.json | 2 +-
src/app/db/schema/portfolio.ts | 37 ++++++++++
src/app/db/schema/recommendations.ts | 7 ++
src/app/db/schema/uploaded_files.ts | 29 +++++++-
.../(portfolio)/settings/user-access/page.tsx | 2 +
.../your-projects/live/LiveTracker.tsx | 51 +++++++++++++-
.../(portfolio)/your-projects/live/page.tsx | 68 ++++++++++++++++++-
.../your-projects/live/transforms.ts | 2 +-
.../(portfolio)/your-projects/live/types.ts | 11 +++
9 files changed, 198 insertions(+), 11 deletions(-)
diff --git a/src/app/db/migrations/meta/_journal.json b/src/app/db/migrations/meta/_journal.json
index 9436ff4e..9309f692 100644
--- a/src/app/db/migrations/meta/_journal.json
+++ b/src/app/db/migrations/meta/_journal.json
@@ -1193,4 +1193,4 @@
"breakpoints": true
}
]
-}
\ No newline at end of file
+}
diff --git a/src/app/db/schema/portfolio.ts b/src/app/db/schema/portfolio.ts
index d424634b..8e231bc8 100644
--- a/src/app/db/schema/portfolio.ts
+++ b/src/app/db/schema/portfolio.ts
@@ -7,6 +7,7 @@ import {
pgEnum,
integer,
bigint,
+ unique,
} from "drizzle-orm/pg-core";
import { user } from "./users";
import { InferModel } from "drizzle-orm";
@@ -124,7 +125,43 @@ export const portfolioUsers = pgTable("portfolioUsers", {
.notNull(),
});
+export const PortfolioCapability: [string, ...string[]] = [
+ "approver",
+ "contractor",
+];
+export type PortfolioCapabilityType = "approver" | "contractor";
+
+export const portfolioCapabilityEnum = pgEnum(
+ "portfolio_capability",
+ PortfolioCapability as [string, ...string[]],
+);
+
+export const portfolioCapabilities = pgTable(
+ "portfolio_capabilities",
+ {
+ id: bigserial("id", { mode: "bigint" }).primaryKey(),
+ userId: bigint("user_id", { mode: "bigint" })
+ .notNull()
+ .references(() => user.id),
+ portfolioId: bigint("portfolio_id", { mode: "bigint" })
+ .notNull()
+ .references(() => portfolio.id),
+ capability: portfolioCapabilityEnum("capability").notNull(),
+ createdAt: timestamp("created_at", { precision: 6, withTimezone: true })
+ .defaultNow()
+ .notNull(),
+ updatedAt: timestamp("updated_at", { precision: 6, withTimezone: true })
+ .defaultNow()
+ .notNull(),
+ },
+ (table) => [unique().on(table.userId, table.portfolioId, table.capability)],
+);
+
export type Portfolio = InferModel;
export type NewPortfolio = InferModel;
export type PortfolioUsers = InferModel;
export type NewPortfolioUsers = InferModel;
+export type PortfolioCapabilities = InferModel<
+ typeof portfolioCapabilities,
+ "select"
+>;
diff --git a/src/app/db/schema/recommendations.ts b/src/app/db/schema/recommendations.ts
index 3ffdff24..2cef2bd3 100644
--- a/src/app/db/schema/recommendations.ts
+++ b/src/app/db/schema/recommendations.ts
@@ -58,6 +58,13 @@ export const measureTypeEnum = pgEnum("measure_type", [
// Other fabric / hot water
"hot_water_tank_insulation",
"sealing_open_fireplace",
+
+ // Contractor workflow measures
+ "damp_mould",
+ "door_undercut",
+ "extractor_fan",
+ "loft_board",
+ "trickle_vent",
]);
export const recommendation = pgTable(
diff --git a/src/app/db/schema/uploaded_files.ts b/src/app/db/schema/uploaded_files.ts
index 18d99a07..abf2fa1d 100644
--- a/src/app/db/schema/uploaded_files.ts
+++ b/src/app/db/schema/uploaded_files.ts
@@ -1,6 +1,8 @@
import { bigint, bigserial, pgEnum, pgTable, text, timestamp } from "drizzle-orm/pg-core";
+import { user } from "./users";
export const fileType = pgEnum("file_type", [
+ // Survey documents (existing)
"photo_pack",
"site_note",
"rd_sap_site_note",
@@ -12,14 +14,33 @@ export const fileType = pgEnum("file_type", [
"pas_2023_occupancy",
"ecmk_site_note",
"ecmk_rd_sap_site_note",
- "ecmk_survey_xml"
+ "ecmk_survey_xml",
+ // Contractor install documentation
+ "pre_photo",
+ "mid_photo",
+ "post_photo",
+ "pre_installation_building_inspection",
+ "claim_of_compliance",
+ "handover_pack",
+ "insurance_guarantee",
+ "installer_qualifications",
+ "mcs_compliance_certificate",
+ "minor_works_electrical_certificate",
+ "point_of_work_risk_assessment",
+ "installer_feedback",
+ "workmanship_warranty",
+ "g98_notification",
+ "certificate_of_conformity",
+ "ventilation_assessment_checklist",
+ "contractor_other",
]);
export const fileSource = pgEnum("file_source", [
"pas hub",
"sharepoint",
"hubspot",
- "ecmk"
+ "ecmk",
+ "contractor",
]);
export const uploadedFiles = pgTable(
@@ -36,6 +57,8 @@ export const uploadedFiles = pgTable(
hubsotDealId: text("hubspot_deal_id"),
hubspotListingId: bigint("hubspot_listing_id", { mode: "bigint" }),
fileType: fileType("file_type"),
- source: fileSource("file_source")
+ source: fileSource("file_source"),
+ measureName: text("measure_name"),
+ uploadedBy: bigint("uploaded_by", { mode: "bigint" }).references(() => user.id),
}
);
\ No newline at end of file
diff --git a/src/app/portfolio/[slug]/(portfolio)/settings/user-access/page.tsx b/src/app/portfolio/[slug]/(portfolio)/settings/user-access/page.tsx
index 5d749abd..ae98d36d 100644
--- a/src/app/portfolio/[slug]/(portfolio)/settings/user-access/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/settings/user-access/page.tsx
@@ -1,4 +1,5 @@
import { UsersPermissionsCard } from "../UsersPermissionsCard";
+import { CapabilitiesCard } from "../CapabilitiesCard";
export default async function UserAccessPage(props: {
params: Promise<{ slug: string }>;
@@ -8,6 +9,7 @@ export default async function UserAccessPage(props: {
return (
+
);
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
index 502bd71d..a9138abf 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
@@ -9,10 +9,11 @@ import {
TabsTrigger,
} from "@/app/shadcn_components/ui/tabs";
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
-import { BarChart2, Table2, FolderOpen } from "lucide-react";
+import { BarChart2, Table2, FolderOpen, Wrench } from "lucide-react";
import DrillDownTable from "./DrillDownTable";
import PropertyTable from "./PropertyTable";
import DocumentTable from "./DocumentTable";
+import MeasuresTable from "./MeasuresTable";
import type { HubspotDeal } from "./types";
import PropertyDrawer from "./PropertyDrawer";
import PropertyDetailDrawer from "./PropertyDetailDrawer";
@@ -30,9 +31,12 @@ export default function LiveTracker({
totalDeals,
majorConditionDeals,
docStatusMap,
+ userCapability,
+ approvalsByDeal,
+ portfolioId,
}: LiveTrackerProps) {
// ── Tab state ────────────────────────────────────────────────────────
- const [activeTab, setActiveTab] = useState<"analytics" | "properties" | "documents">(
+ const [activeTab, setActiveTab] = useState<"analytics" | "properties" | "documents" | "measures">(
"analytics",
);
@@ -94,7 +98,7 @@ export default function LiveTracker({
setActiveTab(v as "analytics" | "properties" | "documents")}
+ onValueChange={(v) => setActiveTab(v as "analytics" | "properties" | "documents" | "measures")}
>
{/* Tab bar */}
@@ -119,6 +123,13 @@ export default function LiveTracker({
Document Management
+
+
+ Measures
+
{/* Analytics tab */}
@@ -207,6 +218,40 @@ export default function LiveTracker({
/>
+
+ {/* Measures tab */}
+
+
+ {projects.length > 1 && (
+
+ Project:
+
+
+ )}
+
+
+
{/* ── Drill-down table modal ─────────────────────────────────────── */}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index f7002fc1..1a8fc5ef 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -1,7 +1,7 @@
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
import { redirect } from "next/navigation";
-import { eq, inArray } from "drizzle-orm";
+import { and, eq, inArray } from "drizzle-orm";
import LiveTracker from "./LiveTracker";
import { computeLiveTrackerData } from "./transforms";
import { db } from "@/app/db/db";
@@ -9,7 +9,10 @@ import { hubspotDealData } from "@/app/db/schema/crm/hubspot_deal_table";
import { uploadedFiles } from "@/app/db/schema/uploaded_files";
import { portfolioOrganisation } from "@/app/db/schema/portfolio_organisation";
import { organisation } from "@/app/db/schema/organisation";
-import type { HubspotDeal, DocStatusMap, DocStatus } from "./types";
+import { portfolioCapabilities } from "@/app/db/schema/portfolio";
+import { dealApprovals, dealApprovedMeasures } from "@/app/db/schema/approvals";
+import { user as userTable } from "@/app/db/schema/users";
+import type { HubspotDeal, DocStatusMap, DocStatus, PortfolioCapabilityType, ApprovalsByDeal } from "./types";
import { EXPECTED_SURVEY_DOC_TYPES } from "./types";
import type { InferSelectModel } from "drizzle-orm";
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
@@ -120,6 +123,59 @@ export default async function LiveReportingPage(props: {
const deals = rawDeals.map(mapDbRowToHubspotDeal);
const trackerData = computeLiveTrackerData(deals);
+ // Fetch current user's portfolio capability (approver / contractor)
+ let userCapability: PortfolioCapabilityType = null;
+ const userEmail = user?.user?.email;
+ if (userEmail) {
+ const userRow = await db
+ .select({ id: userTable.id })
+ .from(userTable)
+ .where(eq(userTable.email, userEmail))
+ .limit(1);
+
+ if (userRow[0]) {
+ const capRow = await db
+ .select({ capability: portfolioCapabilities.capability })
+ .from(portfolioCapabilities)
+ .where(
+ and(
+ eq(portfolioCapabilities.portfolioId, BigInt(portfolioId)),
+ eq(portfolioCapabilities.userId, userRow[0].id),
+ ),
+ )
+ .limit(1);
+ userCapability = (capRow[0]?.capability as PortfolioCapabilityType) ?? null;
+ }
+ }
+
+ // Fetch deal approvals for all deals in scope
+ const approvalsByDeal: ApprovalsByDeal = {};
+ const dealIds = deals.map((d) => d.dealId).filter(Boolean);
+ if (dealIds.length > 0) {
+ const approvalRows = await db
+ .select({ id: dealApprovals.id, hubspotDealId: dealApprovals.hubspotDealId })
+ .from(dealApprovals)
+ .where(inArray(dealApprovals.hubspotDealId, dealIds));
+
+ if (approvalRows.length > 0) {
+ const approvalIds = approvalRows.map((a) => a.id);
+ const measureRows = await db
+ .select({
+ dealApprovalId: dealApprovedMeasures.dealApprovalId,
+ measureName: dealApprovedMeasures.measureName,
+ })
+ .from(dealApprovedMeasures)
+ .where(inArray(dealApprovedMeasures.dealApprovalId, approvalIds));
+
+ const approvalById = new Map(approvalRows.map((a) => [a.id.toString(), a.hubspotDealId]));
+ for (const m of measureRows) {
+ const dealId = approvalById.get(m.dealApprovalId.toString());
+ if (!dealId) continue;
+ (approvalsByDeal[dealId] ??= []).push(m.measureName);
+ }
+ }
+ }
+
// Fetch survey document status for all properties
const uprnList = deals
.map((d) => d.uprn)
@@ -158,7 +214,13 @@ export default async function LiveReportingPage(props: {
return (
{pageHeader}
-
+
);
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts
index fe1da877..b829c914 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/transforms.ts
@@ -375,7 +375,7 @@ export function computeOutcomeSlices(deals: ClassifiedDeal[]): OutcomeSlice[] {
// -----------------------------------------------------------------------
export function computeLiveTrackerData(
rawDeals: HubspotDeal[]
-): Omit {
+): Omit {
// Classify all deals (add displayStage field)
const classified = classifyDeals(rawDeals);
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
index 40fa764f..ac808ce4 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
@@ -161,6 +161,14 @@ export type ProjectData = {
allDeals: ClassifiedDeal[]; // for table drill-downs within project
};
+// -----------------------------------------------------------------------
+// Portfolio capability for the current viewing user
+// -----------------------------------------------------------------------
+export type PortfolioCapabilityType = "approver" | "contractor" | null;
+
+// Approved measure names per HubSpot deal ID
+export type ApprovalsByDeal = Record;
+
// -----------------------------------------------------------------------
// Top-level props for LiveTracker (client root)
// -----------------------------------------------------------------------
@@ -169,6 +177,9 @@ export type LiveTrackerProps = {
totalDeals: number;
majorConditionDeals: ClassifiedDeal[]; // for Awaab's Law card
docStatusMap: DocStatusMap;
+ userCapability: PortfolioCapabilityType;
+ approvalsByDeal: ApprovalsByDeal;
+ portfolioId: string;
};
// -----------------------------------------------------------------------
From c6bd99c980df72aa11e1b35ba7f23a645c71f5db Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Thu, 16 Apr 2026 21:13:35 +0000
Subject: [PATCH 06/43] adding missing files
---
.../[portfolioId]/approvals/route.ts | 191 ++++++++++
.../[portfolioId]/capabilities/route.ts | 171 +++++++++
.../api/upload/contractor-install/route.ts | 68 ++++
src/app/db/schema/approvals.ts | 54 +++
.../(portfolio)/settings/CapabilitiesCard.tsx | 230 ++++++++++++
.../your-projects/live/InstallUploadModal.tsx | 272 ++++++++++++++
.../your-projects/live/MeasuresTable.tsx | 355 ++++++++++++++++++
7 files changed, 1341 insertions(+)
create mode 100644 src/app/api/portfolio/[portfolioId]/approvals/route.ts
create mode 100644 src/app/api/portfolio/[portfolioId]/capabilities/route.ts
create mode 100644 src/app/api/upload/contractor-install/route.ts
create mode 100644 src/app/db/schema/approvals.ts
create mode 100644 src/app/portfolio/[slug]/(portfolio)/settings/CapabilitiesCard.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
diff --git a/src/app/api/portfolio/[portfolioId]/approvals/route.ts b/src/app/api/portfolio/[portfolioId]/approvals/route.ts
new file mode 100644
index 00000000..4ca7dac1
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/approvals/route.ts
@@ -0,0 +1,191 @@
+import { db } from "@/app/db/db";
+import { NextRequest, NextResponse } from "next/server";
+import { dealApprovals, dealApprovedMeasures } from "@/app/db/schema/approvals";
+import {
+ portfolioCapabilities,
+ portfolioUsers,
+} from "@/app/db/schema/portfolio";
+import { user } from "@/app/db/schema/users";
+import { and, eq, inArray } from "drizzle-orm";
+import { z } from "zod";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+
+async function getRequestingUserId(email: string): Promise {
+ const rows = await db
+ .select({ id: user.id })
+ .from(user)
+ .where(eq(user.email, email))
+ .limit(1);
+ return rows[0]?.id ?? null;
+}
+
+async function hasApproverCapability(
+ portfolioId: bigint,
+ userId: bigint,
+): Promise {
+ const rows = await db
+ .select({ id: portfolioCapabilities.id })
+ .from(portfolioCapabilities)
+ .where(
+ and(
+ eq(portfolioCapabilities.portfolioId, portfolioId),
+ eq(portfolioCapabilities.userId, userId),
+ eq(portfolioCapabilities.capability, "approver"),
+ ),
+ )
+ .limit(1);
+ return rows.length > 0;
+}
+
+// GET — return all approved measures grouped by hubspot deal id
+// Query param: dealIds (comma-separated)
+export async function GET(
+ req: NextRequest,
+ props: { params: Promise<{ portfolioId: string }> },
+) {
+ const { portfolioId } = await props.params;
+ const url = new URL(req.url);
+ const dealIdsParam = url.searchParams.get("dealIds");
+
+ if (!dealIdsParam) {
+ return NextResponse.json({});
+ }
+
+ const dealIds = dealIdsParam.split(",").filter(Boolean);
+ if (dealIds.length === 0) {
+ return NextResponse.json({});
+ }
+
+ try {
+ // Fetch most recent approval per deal + its measures
+ const approvals = await db
+ .select({
+ id: dealApprovals.id,
+ hubspotDealId: dealApprovals.hubspotDealId,
+ approvedAt: dealApprovals.approvedAt,
+ })
+ .from(dealApprovals)
+ .where(inArray(dealApprovals.hubspotDealId, dealIds));
+
+ if (approvals.length === 0) {
+ return NextResponse.json({});
+ }
+
+ const approvalIds = approvals.map((a) => a.id);
+ const measures = await db
+ .select({
+ dealApprovalId: dealApprovedMeasures.dealApprovalId,
+ measureName: dealApprovedMeasures.measureName,
+ })
+ .from(dealApprovedMeasures)
+ .where(inArray(dealApprovedMeasures.dealApprovalId, approvalIds));
+
+ // Build map: dealId -> approved measure names[]
+ const approvalById = new Map(approvals.map((a) => [a.id.toString(), a]));
+ const result: Record = {};
+
+ for (const m of measures) {
+ const approval = approvalById.get(m.dealApprovalId.toString());
+ if (!approval) continue;
+ const dealId = approval.hubspotDealId;
+ if (!result[dealId]) result[dealId] = [];
+ result[dealId].push(m.measureName);
+ }
+
+ return NextResponse.json(result);
+ } catch (err) {
+ console.error("GET /approvals error:", err);
+ return NextResponse.json(
+ { error: "Failed to fetch approvals" },
+ { status: 500 },
+ );
+ }
+}
+
+// POST — save approvals for one or more deals (replaces previous approvals)
+export async function POST(
+ req: NextRequest,
+ props: { params: Promise<{ portfolioId: string }> },
+) {
+ const session = await getServerSession(AuthOptions);
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
+ }
+
+ const { portfolioId } = await props.params;
+ const pId = BigInt(portfolioId);
+
+ const userId = await getRequestingUserId(session.user.email);
+ if (!userId) {
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
+ }
+
+ const isApprover = await hasApproverCapability(pId, userId);
+ if (!isApprover) {
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
+ }
+
+ const bodySchema = z.object({
+ deals: z.array(
+ z.object({
+ hubspotDealId: z.string(),
+ approvedMeasures: z.array(z.string()),
+ }),
+ ),
+ });
+
+ let body: z.infer;
+ try {
+ body = bodySchema.parse(await req.json());
+ } catch {
+ return NextResponse.json({ error: "Invalid body" }, { status: 400 });
+ }
+
+ try {
+ for (const deal of body.deals) {
+ // Delete previous approvals for this deal (replace approach)
+ const existing = await db
+ .select({ id: dealApprovals.id })
+ .from(dealApprovals)
+ .where(eq(dealApprovals.hubspotDealId, deal.hubspotDealId));
+
+ if (existing.length > 0) {
+ const existingIds = existing.map((e) => e.id);
+ await db
+ .delete(dealApprovedMeasures)
+ .where(inArray(dealApprovedMeasures.dealApprovalId, existingIds));
+ await db
+ .delete(dealApprovals)
+ .where(inArray(dealApprovals.id, existingIds));
+ }
+
+ if (deal.approvedMeasures.length === 0) continue;
+
+ // Insert new approval session
+ const [approval] = await db
+ .insert(dealApprovals)
+ .values({
+ hubspotDealId: deal.hubspotDealId,
+ approvedBy: userId,
+ })
+ .returning({ id: dealApprovals.id });
+
+ // Insert individual approved measures (stored as free text matching HubSpot proposedMeasures)
+ await db.insert(dealApprovedMeasures).values(
+ deal.approvedMeasures.map((m) => ({
+ dealApprovalId: approval.id,
+ measureName: m,
+ })),
+ );
+ }
+
+ return NextResponse.json({ success: true });
+ } catch (err) {
+ console.error("POST /approvals error:", err);
+ return NextResponse.json(
+ { error: "Failed to save approvals" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/portfolio/[portfolioId]/capabilities/route.ts b/src/app/api/portfolio/[portfolioId]/capabilities/route.ts
new file mode 100644
index 00000000..8f7f8b02
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/capabilities/route.ts
@@ -0,0 +1,171 @@
+import { db } from "@/app/db/db";
+import { NextRequest, NextResponse } from "next/server";
+import {
+ portfolioUsers,
+ portfolioCapabilities,
+ PortfolioCapabilityType,
+} from "@/app/db/schema/portfolio";
+import { user } from "@/app/db/schema/users";
+import { and, eq } from "drizzle-orm";
+import { z } from "zod";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+
+const CAPABILITY_OPTIONS = ["approver", "contractor"] as const;
+
+async function getRequestingUserRole(portfolioId: bigint, email: string) {
+ const rows = await db
+ .select({ role: portfolioUsers.role })
+ .from(portfolioUsers)
+ .innerJoin(user, eq(user.id, portfolioUsers.userId))
+ .where(
+ and(
+ eq(portfolioUsers.portfolioId, portfolioId),
+ eq(user.email, email),
+ ),
+ )
+ .limit(1);
+ return rows[0]?.role ?? null;
+}
+
+// GET — list all capability assignments for this portfolio
+export async function GET(
+ _req: NextRequest,
+ props: { params: Promise<{ portfolioId: string }> },
+) {
+ const { portfolioId } = await props.params;
+
+ try {
+ const rows = await db
+ .select({
+ id: portfolioCapabilities.id,
+ userId: portfolioCapabilities.userId,
+ capability: portfolioCapabilities.capability,
+ name: user.firstName,
+ email: user.email,
+ })
+ .from(portfolioCapabilities)
+ .leftJoin(user, eq(user.id, portfolioCapabilities.userId))
+ .where(eq(portfolioCapabilities.portfolioId, BigInt(portfolioId)));
+
+ return NextResponse.json(
+ rows.map((r) => ({
+ id: r.id?.toString(),
+ userId: r.userId?.toString(),
+ capability: r.capability,
+ name: r.name ?? null,
+ email: r.email ?? "",
+ })),
+ );
+ } catch (err) {
+ console.error("GET /capabilities error:", err);
+ return NextResponse.json(
+ { error: "Failed to fetch capabilities" },
+ { status: 500 },
+ );
+ }
+}
+
+// POST — assign a capability to a user
+export async function POST(
+ req: NextRequest,
+ props: { params: Promise<{ portfolioId: string }> },
+) {
+ const session = await getServerSession(AuthOptions);
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
+ }
+
+ const { portfolioId } = await props.params;
+ const pId = BigInt(portfolioId);
+
+ const requestingRole = await getRequestingUserRole(pId, session.user.email);
+ if (requestingRole !== "admin" && requestingRole !== "creator") {
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
+ }
+
+ const bodySchema = z.object({
+ userId: z.string(),
+ capability: z.enum(CAPABILITY_OPTIONS),
+ });
+
+ let body: z.infer;
+ try {
+ body = bodySchema.parse(await req.json());
+ } catch {
+ return NextResponse.json({ error: "Invalid body" }, { status: 400 });
+ }
+
+ try {
+ await db
+ .insert(portfolioCapabilities)
+ .values({
+ portfolioId: pId,
+ userId: BigInt(body.userId),
+ capability: body.capability as PortfolioCapabilityType,
+ })
+ .onConflictDoNothing();
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error("POST /capabilities error:", err);
+ return NextResponse.json(
+ { error: "Failed to assign capability" },
+ { status: 500 },
+ );
+ }
+}
+
+// DELETE — remove a capability from a user
+export async function DELETE(
+ req: NextRequest,
+ props: { params: Promise<{ portfolioId: string }> },
+) {
+ const session = await getServerSession(AuthOptions);
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
+ }
+
+ const { portfolioId } = await props.params;
+ const pId = BigInt(portfolioId);
+
+ const requestingRole = await getRequestingUserRole(pId, session.user.email);
+ if (requestingRole !== "admin" && requestingRole !== "creator") {
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
+ }
+
+ const bodySchema = z.object({
+ userId: z.string(),
+ capability: z.enum(CAPABILITY_OPTIONS),
+ });
+
+ let body: z.infer;
+ try {
+ body = bodySchema.parse(await req.json());
+ } catch {
+ return NextResponse.json({ error: "Invalid body" }, { status: 400 });
+ }
+
+ try {
+ await db
+ .delete(portfolioCapabilities)
+ .where(
+ and(
+ eq(portfolioCapabilities.portfolioId, pId),
+ eq(portfolioCapabilities.userId, BigInt(body.userId)),
+ eq(
+ portfolioCapabilities.capability,
+ body.capability as PortfolioCapabilityType,
+ ),
+ ),
+ );
+
+ return NextResponse.json({ success: true }, { status: 200 });
+ } catch (err) {
+ console.error("DELETE /capabilities error:", err);
+ return NextResponse.json(
+ { error: "Failed to remove capability" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/api/upload/contractor-install/route.ts b/src/app/api/upload/contractor-install/route.ts
new file mode 100644
index 00000000..ea05d4bc
--- /dev/null
+++ b/src/app/api/upload/contractor-install/route.ts
@@ -0,0 +1,68 @@
+import { db } from "@/app/db/db";
+import { NextRequest, NextResponse } from "next/server";
+import { uploadedFiles } from "@/app/db/schema/uploaded_files";
+import { user } from "@/app/db/schema/users";
+import { eq } from "drizzle-orm";
+import { z } from "zod";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+
+// POST — record a contractor install document in uploaded_files
+export async function POST(req: NextRequest) {
+ const session = await getServerSession(AuthOptions);
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
+ }
+
+ const bodySchema = z.object({
+ s3FileKey: z.string(),
+ s3FileBucket: z.string(),
+ fileType: z.string(),
+ measureName: z.string().optional(),
+ uprn: z.string().optional(),
+ hubspotDealId: z.string().optional(),
+ landlordPropertyId: z.string().optional(),
+ });
+
+ let body: z.infer;
+ try {
+ body = bodySchema.parse(await req.json());
+ } catch {
+ return NextResponse.json({ error: "Invalid body" }, { status: 400 });
+ }
+
+ try {
+ // Resolve uploader's user ID
+ const userRow = await db
+ .select({ id: user.id })
+ .from(user)
+ .where(eq(user.email, session.user.email))
+ .limit(1);
+
+ const uploadedBy = userRow[0]?.id ?? null;
+
+ const [inserted] = await db
+ .insert(uploadedFiles)
+ .values({
+ s3FileBucket: body.s3FileBucket,
+ s3FileKey: body.s3FileKey,
+ s3UploadTimestamp: new Date(),
+ fileType: body.fileType as any,
+ source: "contractor",
+ measureName: body.measureName ?? null,
+ uploadedBy: uploadedBy ?? undefined,
+ uprn: body.uprn ? BigInt(body.uprn) : undefined,
+ hubsotDealId: body.hubspotDealId ?? null,
+ landlordPropertyId: body.landlordPropertyId ?? null,
+ })
+ .returning({ id: uploadedFiles.id });
+
+ return NextResponse.json({ id: inserted.id.toString() }, { status: 201 });
+ } catch (err) {
+ console.error("POST /upload/contractor-install error:", err);
+ return NextResponse.json(
+ { error: "Failed to record upload" },
+ { status: 500 },
+ );
+ }
+}
diff --git a/src/app/db/schema/approvals.ts b/src/app/db/schema/approvals.ts
new file mode 100644
index 00000000..92031500
--- /dev/null
+++ b/src/app/db/schema/approvals.ts
@@ -0,0 +1,54 @@
+import {
+ bigserial,
+ text,
+ timestamp,
+ pgTable,
+ bigint,
+ index,
+} from "drizzle-orm/pg-core";
+import { user } from "./users";
+import { InferModel } from "drizzle-orm";
+
+export const dealApprovals = pgTable(
+ "deal_approvals",
+ {
+ id: bigserial("id", { mode: "bigint" }).primaryKey(),
+ hubspotDealId: text("hubspot_deal_id").notNull(),
+ approvedBy: bigint("approved_by", { mode: "bigint" })
+ .notNull()
+ .references(() => user.id),
+ approvedAt: timestamp("approved_at", { withTimezone: true })
+ .defaultNow()
+ .notNull(),
+ notes: text("notes"),
+ createdAt: timestamp("created_at", { withTimezone: true })
+ .defaultNow()
+ .notNull(),
+ },
+ (table) => [index("idx_deal_approvals_deal_id").on(table.hubspotDealId)],
+);
+
+export const dealApprovedMeasures = pgTable(
+ "deal_approved_measures",
+ {
+ id: bigserial("id", { mode: "bigint" }).primaryKey(),
+ dealApprovalId: bigint("deal_approval_id", { mode: "bigint" })
+ .notNull()
+ .references(() => dealApprovals.id, { onDelete: "cascade" }),
+ // Stored as text to match free-text proposedMeasures from HubSpot
+ measureName: text("measure_name").notNull(),
+ createdAt: timestamp("created_at", { withTimezone: true })
+ .defaultNow()
+ .notNull(),
+ },
+ (table) => [
+ index("idx_deal_approved_measures_approval_id").on(table.dealApprovalId),
+ ],
+);
+
+export type DealApproval = InferModel;
+export type NewDealApproval = InferModel;
+export type DealApprovedMeasure = InferModel<
+ typeof dealApprovedMeasures,
+ "select"
+>;
diff --git a/src/app/portfolio/[slug]/(portfolio)/settings/CapabilitiesCard.tsx b/src/app/portfolio/[slug]/(portfolio)/settings/CapabilitiesCard.tsx
new file mode 100644
index 00000000..533c2e3c
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/settings/CapabilitiesCard.tsx
@@ -0,0 +1,230 @@
+"use client";
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/app/shadcn_components/ui/table";
+import { Button } from "@/app/shadcn_components/ui/button";
+import { Badge } from "@/app/shadcn_components/ui/badge";
+import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
+
+type Capability = "approver" | "contractor";
+
+type CapabilityEntry = {
+ id: string;
+ userId: string;
+ capability: Capability;
+ name: string | null;
+ email: string;
+};
+
+type CapabilityMap = Record;
+
+async function getCapabilities(portfolioId: string): Promise {
+ const res = await fetch(`/api/portfolio/${portfolioId}/capabilities`);
+ if (!res.ok) throw new Error("Failed to fetch capabilities");
+ return res.json();
+}
+
+async function getCollaborators(
+ portfolioId: string,
+): Promise<{ userId: string; name: string | null; email: string }[]> {
+ const res = await fetch(`/api/portfolio/${portfolioId}/colloborators`);
+ if (!res.ok) throw new Error("Failed to fetch collaborators");
+ const json = await res.json();
+ const users = Array.isArray(json) ? json : json.users ?? [];
+ return users.map((u: any) => ({
+ userId: String(u.userId),
+ name: u.name ?? null,
+ email: u.email ?? "",
+ }));
+}
+
+async function assignCapability(
+ portfolioId: string,
+ userId: string,
+ capability: Capability,
+): Promise {
+ const res = await fetch(`/api/portfolio/${portfolioId}/capabilities`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ userId, capability }),
+ });
+ if (!res.ok) throw new Error("Failed to assign capability");
+}
+
+async function removeCapability(
+ portfolioId: string,
+ userId: string,
+ capability: Capability,
+): Promise {
+ const res = await fetch(`/api/portfolio/${portfolioId}/capabilities`, {
+ method: "DELETE",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ userId, capability }),
+ });
+ if (!res.ok) throw new Error("Failed to remove capability");
+}
+
+export function CapabilitiesCard({ portfolioId }: { portfolioId: string }) {
+ const queryClient = useQueryClient();
+ const queryKey = ["portfolioCapabilities", portfolioId];
+
+ const { data: entries = [], isLoading: loadingCaps } = useQuery({
+ queryKey,
+ queryFn: () => getCapabilities(portfolioId),
+ enabled: !!portfolioId,
+ refetchOnWindowFocus: false,
+ });
+
+ const { data: collaborators = [], isLoading: loadingCollabs } = useQuery({
+ queryKey: ["portfolioUsers", portfolioId],
+ queryFn: () => getCollaborators(portfolioId),
+ enabled: !!portfolioId,
+ refetchOnWindowFocus: false,
+ });
+
+ const isLoading = loadingCaps || loadingCollabs;
+
+ // Build a map: userId -> { capabilities: [] }
+ const capMap: CapabilityMap = {};
+ for (const c of collaborators) {
+ capMap[c.userId] = { name: c.name, email: c.email, capabilities: [] };
+ }
+ for (const e of entries) {
+ if (capMap[e.userId]) {
+ capMap[e.userId].capabilities.push(e.capability);
+ }
+ }
+
+ const toggleMutation = useMutation({
+ mutationFn: ({
+ userId,
+ capability,
+ has,
+ }: {
+ userId: string;
+ capability: Capability;
+ has: boolean;
+ }) =>
+ has
+ ? removeCapability(portfolioId, userId, capability)
+ : assignCapability(portfolioId, userId, capability),
+ onSettled: () => {
+ queryClient.invalidateQueries({ queryKey });
+ },
+ });
+
+ const rows = Object.entries(capMap);
+
+ return (
+
+
+
+
+
+ Project Roles:
+
+ Assign approver or contractor capabilities to users
+
+
+
+
+
+
+
+
+
+ Name
+ Email
+ Approver
+ Contractor
+
+
+
+ {isLoading ? (
+
+
+ Loading…
+
+
+ ) : rows.length === 0 ? (
+
+
+ No collaborators yet. Add users in the section above first.
+
+
+ ) : (
+ rows.map(([userId, { name, email, capabilities }]) => (
+
+ {name || "—"}
+ {email}
+
+
+ toggleMutation.mutate({ userId, capability: "approver", has })
+ }
+ />
+
+
+
+ toggleMutation.mutate({ userId, capability: "contractor", has })
+ }
+ />
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ );
+}
+
+function CapabilityToggle({
+ has,
+ capability,
+ isPending,
+ onToggle,
+}: {
+ has: boolean;
+ capability: Capability;
+ isPending: boolean;
+ onToggle: (has: boolean) => void;
+}) {
+ return (
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx
new file mode 100644
index 00000000..d7c83bf1
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx
@@ -0,0 +1,272 @@
+"use client";
+
+import { useState } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/app/shadcn_components/ui/dialog";
+import { Button } from "@/app/shadcn_components/ui/button";
+import { Input } from "@/app/shadcn_components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/app/shadcn_components/ui/select";
+import { uploadFileToS3 } from "@/app/utils/s3";
+import type { ClassifiedDeal } from "./types";
+
+type Props = {
+ deal: ClassifiedDeal;
+ onClose: () => void;
+};
+
+// Contractor-specific file type options grouped for the UI
+const FILE_TYPE_OPTIONS: { value: string; label: string; group: string }[] = [
+ // Install photos
+ { value: "pre_photo", label: "Pre Photo", group: "Install Photos" },
+ { value: "mid_photo", label: "Mid Photo", group: "Install Photos" },
+ { value: "post_photo", label: "Post Photo", group: "Install Photos" },
+ // Pre-installation
+ { value: "pre_installation_building_inspection", label: "Pre-Installation Building Inspection (PIBI)", group: "Pre-Installation" },
+ { value: "point_of_work_risk_assessment", label: "Point of Work Risk Assessment", group: "Pre-Installation" },
+ // Compliance
+ { value: "claim_of_compliance", label: "Claim of Compliance (PAS 2030)", group: "Compliance" },
+ { value: "mcs_compliance_certificate", label: "MCS Compliance Certificate", group: "Compliance" },
+ { value: "certificate_of_conformity", label: "Certificate of Conformity", group: "Compliance" },
+ { value: "minor_works_electrical_certificate", label: "Minor Works Electrical Certificate", group: "Compliance" },
+ // Handover
+ { value: "handover_pack", label: "Handover Documents / Pack", group: "Handover" },
+ { value: "workmanship_warranty", label: "Workmanship Warranty", group: "Handover" },
+ { value: "insurance_guarantee", label: "Insurance Backed Guarantee (IBG)", group: "Handover" },
+ { value: "g98_notification", label: "G98 / G99 Notification", group: "Handover" },
+ { value: "ventilation_assessment_checklist", label: "Ventilation Assessment Checklist", group: "Handover" },
+ // Qualifications
+ { value: "installer_qualifications", label: "Installer Qualifications", group: "Qualifications" },
+ { value: "installer_feedback", label: "Installer Feedback", group: "Other" },
+ { value: "contractor_other", label: "Other", group: "Other" },
+];
+
+const FILE_TYPE_GROUPS = [
+ "Install Photos",
+ "Pre-Installation",
+ "Compliance",
+ "Handover",
+ "Qualifications",
+ "Other",
+];
+
+function parseMeasures(raw: string | null | undefined): string[] {
+ if (!raw) return [];
+ return raw.split(",").map((m) => m.trim()).filter(Boolean);
+}
+
+function contentTypeFor(ext: string): string {
+ const e = ext.toLowerCase();
+ if (e === "pdf") return "application/pdf";
+ if (e === "xml") return "application/xml";
+ if (["jpg", "jpeg"].includes(e)) return "image/jpeg";
+ if (e === "png") return "image/png";
+ return "application/octet-stream";
+}
+
+async function getPresignedUrl(path: string, contentType: string): Promise {
+ const res = await fetch("/api/upload/retrofit-energy-assessments", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ path, contentType, expiresInSeconds: 300 }),
+ });
+ if (!res.ok) throw new Error("Failed to get presigned URL");
+ const { url } = await res.json();
+ return url;
+}
+
+async function recordUpload(payload: {
+ s3FileKey: string;
+ s3FileBucket: string;
+ fileType: string;
+ measureName?: string;
+ uprn?: string;
+ hubspotDealId?: string;
+ landlordPropertyId?: string;
+}) {
+ const res = await fetch("/api/upload/contractor-install", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+ if (!res.ok) throw new Error("Failed to record upload");
+}
+
+export default function InstallUploadModal({ deal, onClose }: Props) {
+ const measures = parseMeasures(deal.proposedMeasures);
+ const [file, setFile] = useState(null);
+ const [selectedMeasure, setSelectedMeasure] = useState(
+ measures[0] ?? "",
+ );
+ const [selectedDocType, setSelectedDocType] = useState("");
+ const [submitting, setSubmitting] = useState(false);
+ const [error, setError] = useState(null);
+
+ function handleFileChange(e: React.ChangeEvent) {
+ const f = e.target.files?.[0] ?? null;
+ setFile(f);
+ setError(null);
+ }
+
+ async function handleUpload() {
+ if (!file || !selectedDocType) return;
+ setSubmitting(true);
+ setError(null);
+
+ try {
+ const timestamp = new Date()
+ .toISOString()
+ .replace(/[-:]/g, "")
+ .replace("T", "_")
+ .split(".")[0];
+ const ext = (file.name.split(".").pop() || "bin").toLowerCase();
+ const ct = contentTypeFor(ext);
+
+ const s3Key = `contractor-install/${deal.dealId}/${selectedDocType}/${timestamp}.${ext}`;
+
+ const presignedUrl = await getPresignedUrl(s3Key, ct);
+
+ await uploadFileToS3({ presignedUrl, file, contentType: ct });
+
+ // Extract bucket from presigned URL (format: https://bucket.s3.region.amazonaws.com/...)
+ const urlObj = new URL(presignedUrl);
+ const bucket = urlObj.hostname.split(".")[0];
+
+ await recordUpload({
+ s3FileKey: s3Key,
+ s3FileBucket: bucket,
+ fileType: selectedDocType,
+ measureName: selectedMeasure || undefined,
+ uprn: deal.uprn ?? undefined,
+ hubspotDealId: deal.dealId,
+ landlordPropertyId: deal.landlordPropertyId ?? undefined,
+ });
+
+ onClose();
+ } catch (err) {
+ console.error(err);
+ setError("Upload failed. Please try again.");
+ } finally {
+ setSubmitting(false);
+ }
+ }
+
+ const canUpload = !!file && !!selectedDocType && !submitting;
+
+ return (
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
new file mode 100644
index 00000000..2aa61f5f
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
@@ -0,0 +1,355 @@
+"use client";
+
+import { useMemo, useState } from "react";
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/app/shadcn_components/ui/table";
+import { Input } from "@/app/shadcn_components/ui/input";
+import { Button } from "@/app/shadcn_components/ui/button";
+import { Badge } from "@/app/shadcn_components/ui/badge";
+import { Checkbox } from "@/app/shadcn_components/ui/checkbox";
+import { Search, Upload, Save } from "lucide-react";
+import { STAGE_COLORS } from "./types";
+import type { ClassifiedDeal, PortfolioCapabilityType, ApprovalsByDeal } from "./types";
+import InstallUploadModal from "./InstallUploadModal";
+
+type Props = {
+ data: ClassifiedDeal[];
+ userCapability: PortfolioCapabilityType;
+ approvalsByDeal: ApprovalsByDeal;
+ portfolioId: string;
+};
+
+function parseMeasures(raw: string | null | undefined): string[] {
+ if (!raw) return [];
+ return raw
+ .split(",")
+ .map((m) => m.trim())
+ .filter(Boolean);
+}
+
+function ApprovalStatus({
+ proposed,
+ approved,
+}: {
+ proposed: string[];
+ approved: string[];
+}) {
+ if (proposed.length === 0) return null;
+ const approvedSet = new Set(approved);
+ const approvedCount = proposed.filter((m) => approvedSet.has(m)).length;
+
+ if (approvedCount === 0) {
+ return (
+
+ Pending
+
+ );
+ }
+ if (approvedCount === proposed.length) {
+ return (
+
+ Fully Approved
+
+ );
+ }
+ return (
+
+ {approvedCount}/{proposed.length} Approved
+
+ );
+}
+
+async function saveApprovals(
+ portfolioId: string,
+ deals: { hubspotDealId: string; approvedMeasures: string[] }[],
+) {
+ const res = await fetch(`/api/portfolio/${portfolioId}/approvals`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ deals }),
+ });
+ if (!res.ok) throw new Error("Failed to save approvals");
+}
+
+export default function MeasuresTable({
+ data,
+ userCapability,
+ approvalsByDeal,
+ portfolioId,
+}: Props) {
+ const [search, setSearch] = useState("");
+ const [pendingChanges, setPendingChanges] = useState<
+ Record>
+ >({});
+ const [uploadDeal, setUploadDeal] = useState(null);
+ const [savedApprovals, setSavedApprovals] =
+ useState(approvalsByDeal);
+
+ // Filter to only properties with proposed measures
+ const dealsWithMeasures = useMemo(
+ () => data.filter((d) => d.proposedMeasures),
+ [data],
+ );
+
+ const filtered = useMemo(() => {
+ const q = search.toLowerCase();
+ if (!q) return dealsWithMeasures;
+ return dealsWithMeasures.filter(
+ (d) =>
+ d.dealname?.toLowerCase().includes(q) ||
+ d.landlordPropertyId?.toLowerCase().includes(q) ||
+ d.proposedMeasures?.toLowerCase().includes(q),
+ );
+ }, [dealsWithMeasures, search]);
+
+ const hasPendingChanges = Object.keys(pendingChanges).length > 0;
+
+ const saveMutation = useMutation({
+ mutationFn: () => {
+ const deals = Object.entries(pendingChanges).map(([dealId, measures]) => ({
+ hubspotDealId: dealId,
+ approvedMeasures: Array.from(measures),
+ }));
+ return saveApprovals(portfolioId, deals);
+ },
+ onSuccess: () => {
+ // Merge pending changes into saved approvals
+ setSavedApprovals((prev) => {
+ const next = { ...prev };
+ for (const [dealId, measures] of Object.entries(pendingChanges)) {
+ next[dealId] = Array.from(measures);
+ }
+ return next;
+ });
+ setPendingChanges({});
+ },
+ });
+
+ function toggleMeasure(dealId: string, measure: string, currentApproved: string[]) {
+ setPendingChanges((prev) => {
+ // Start from either pending state or the saved state
+ const current =
+ prev[dealId] !== undefined
+ ? new Set(prev[dealId])
+ : new Set(currentApproved);
+
+ if (current.has(measure)) {
+ current.delete(measure);
+ } else {
+ current.add(measure);
+ }
+
+ // If the pending set equals the saved set, remove from pending
+ const saved = new Set(savedApprovals[dealId] ?? []);
+ const setsEqual =
+ current.size === saved.size && [...current].every((m) => saved.has(m));
+
+ const next = { ...prev };
+ if (setsEqual) {
+ delete next[dealId];
+ } else {
+ next[dealId] = current;
+ }
+ return next;
+ });
+ }
+
+ if (dealsWithMeasures.length === 0) {
+ return (
+
+
+ No properties with proposed measures found in this project.
+
+
+ );
+ }
+
+ return (
+
+ {/* Toolbar */}
+
+
+
+ setSearch(e.target.value)}
+ className="pl-9 h-9 text-sm"
+ />
+
+
+
+ {filtered.length} of {dealsWithMeasures.length} properties
+
+ {userCapability === "approver" && hasPendingChanges && (
+
+ )}
+
+
+
+ {/* Table */}
+
+
+
+
+
+ Address
+
+
+ Stage
+
+
+ Proposed Measures
+
+
+ Status
+
+ {userCapability === "contractor" && (
+
+ Actions
+
+ )}
+
+
+
+ {filtered.map((deal) => {
+ const proposed = parseMeasures(deal.proposedMeasures);
+ const approvedForDeal =
+ pendingChanges[deal.dealId] !== undefined
+ ? Array.from(pendingChanges[deal.dealId])
+ : (savedApprovals[deal.dealId] ?? []);
+ const approvedSet = new Set(approvedForDeal);
+ const stageColor = STAGE_COLORS[deal.displayStage];
+ const hasPending = pendingChanges[deal.dealId] !== undefined;
+
+ return (
+
+ {/* Address */}
+
+
+ {deal.dealname ?? "—"}
+
+ {deal.landlordPropertyId && (
+
+ {deal.landlordPropertyId}
+
+ )}
+
+
+ {/* Stage */}
+
+
+
+ {deal.displayStage}
+
+
+
+ {/* Proposed measures with approval checkboxes */}
+
+
+ {proposed.map((measure) => {
+ const isApproved = approvedSet.has(measure);
+ if (userCapability === "approver") {
+ return (
+
+ );
+ }
+ return (
+
+ {measure}
+
+ );
+ })}
+
+
+
+ {/* Status badge */}
+
+
+
+
+ {/* Contractor upload button */}
+ {userCapability === "contractor" && (
+
+
+
+ )}
+
+ );
+ })}
+
+
+
+
+ {/* Install upload modal */}
+ {uploadDeal && (
+
setUploadDeal(null)}
+ />
+ )}
+
+ );
+}
From 3e872e45d53dc711d05125abfbe65c6904d9e5fb Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Thu, 16 Apr 2026 21:23:34 +0000
Subject: [PATCH 07/43] added migration artefacts
---
.../db/migrations/0172_kind_eddie_brock.sql | 59 +
src/app/db/migrations/meta/0172_snapshot.json | 6718 +++++++++++++++++
src/app/db/migrations/meta/_journal.json | 9 +-
3 files changed, 6785 insertions(+), 1 deletion(-)
create mode 100644 src/app/db/migrations/0172_kind_eddie_brock.sql
create mode 100644 src/app/db/migrations/meta/0172_snapshot.json
diff --git a/src/app/db/migrations/0172_kind_eddie_brock.sql b/src/app/db/migrations/0172_kind_eddie_brock.sql
new file mode 100644
index 00000000..429efb90
--- /dev/null
+++ b/src/app/db/migrations/0172_kind_eddie_brock.sql
@@ -0,0 +1,59 @@
+CREATE TYPE "public"."portfolio_capability" AS ENUM('approver', 'contractor');--> statement-breakpoint
+ALTER TYPE "public"."measure_type" ADD VALUE 'damp_mould';--> statement-breakpoint
+ALTER TYPE "public"."measure_type" ADD VALUE 'door_undercut';--> statement-breakpoint
+ALTER TYPE "public"."measure_type" ADD VALUE 'extractor_fan';--> statement-breakpoint
+ALTER TYPE "public"."measure_type" ADD VALUE 'loft_board';--> statement-breakpoint
+ALTER TYPE "public"."measure_type" ADD VALUE 'trickle_vent';--> statement-breakpoint
+ALTER TYPE "public"."file_source" ADD VALUE 'contractor';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'pre_photo';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'mid_photo';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'post_photo';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'pre_installation_building_inspection';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'claim_of_compliance';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'handover_pack';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'insurance_guarantee';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'installer_qualifications';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'mcs_compliance_certificate';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'minor_works_electrical_certificate';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'point_of_work_risk_assessment';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'installer_feedback';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'workmanship_warranty';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'g98_notification';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'certificate_of_conformity';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'ventilation_assessment_checklist';--> statement-breakpoint
+ALTER TYPE "public"."file_type" ADD VALUE 'contractor_other';--> statement-breakpoint
+CREATE TABLE "deal_approvals" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "hubspot_deal_id" text NOT NULL,
+ "approved_by" bigint NOT NULL,
+ "approved_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "notes" text,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "deal_approved_measures" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "deal_approval_id" bigint NOT NULL,
+ "measure_name" text NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "portfolio_capabilities" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "user_id" bigint NOT NULL,
+ "portfolio_id" bigint NOT NULL,
+ "capability" "portfolio_capability" NOT NULL,
+ "created_at" timestamp (6) with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp (6) with time zone DEFAULT now() NOT NULL,
+ CONSTRAINT "portfolio_capabilities_user_id_portfolio_id_capability_unique" UNIQUE("user_id","portfolio_id","capability")
+);
+--> statement-breakpoint
+ALTER TABLE "uploaded_files" ADD COLUMN "measure_name" text;--> statement-breakpoint
+ALTER TABLE "uploaded_files" ADD COLUMN "uploaded_by" bigint;--> statement-breakpoint
+ALTER TABLE "deal_approvals" ADD CONSTRAINT "deal_approvals_approved_by_user_id_fk" FOREIGN KEY ("approved_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "deal_approved_measures" ADD CONSTRAINT "deal_approved_measures_deal_approval_id_deal_approvals_id_fk" FOREIGN KEY ("deal_approval_id") REFERENCES "public"."deal_approvals"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "portfolio_capabilities" ADD CONSTRAINT "portfolio_capabilities_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "portfolio_capabilities" ADD CONSTRAINT "portfolio_capabilities_portfolio_id_portfolio_id_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "idx_deal_approvals_deal_id" ON "deal_approvals" USING btree ("hubspot_deal_id");--> statement-breakpoint
+CREATE INDEX "idx_deal_approved_measures_approval_id" ON "deal_approved_measures" USING btree ("deal_approval_id");--> statement-breakpoint
+ALTER TABLE "uploaded_files" ADD CONSTRAINT "uploaded_files_uploaded_by_user_id_fk" FOREIGN KEY ("uploaded_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
\ No newline at end of file
diff --git a/src/app/db/migrations/meta/0172_snapshot.json b/src/app/db/migrations/meta/0172_snapshot.json
new file mode 100644
index 00000000..c12c56c0
--- /dev/null
+++ b/src/app/db/migrations/meta/0172_snapshot.json
@@ -0,0 +1,6718 @@
+{
+ "id": "2cd9cc35-10bd-4523-b97a-e1db7eede798",
+ "prevId": "cc0e7e0b-eadb-47e3-8dcc-53754d9e409e",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.postcode_search": {
+ "name": "postcode_search",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_data": {
+ "name": "result_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_updated_at": {
+ "name": "last_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postcode_search_postcode_unique": {
+ "name": "postcode_search_postcode_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "postcode"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deal_approvals": {
+ "name": "deal_approvals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_deal_approvals_deal_id": {
+ "name": "idx_deal_approvals_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deal_approvals_approved_by_user_id_fk": {
+ "name": "deal_approvals_approved_by_user_id_fk",
+ "tableFrom": "deal_approvals",
+ "tableTo": "user",
+ "columnsFrom": [
+ "approved_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deal_approved_measures": {
+ "name": "deal_approved_measures",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "deal_approval_id": {
+ "name": "deal_approval_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_name": {
+ "name": "measure_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_deal_approved_measures_approval_id": {
+ "name": "idx_deal_approved_measures_approval_id",
+ "columns": [
+ {
+ "expression": "deal_approval_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deal_approved_measures_deal_approval_id_deal_approvals_id_fk": {
+ "name": "deal_approved_measures_deal_approval_id_deal_approvals_id_fk",
+ "tableFrom": "deal_approved_measures",
+ "tableTo": "deal_approvals",
+ "columnsFrom": [
+ "deal_approval_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bulk_address_uploads": {
+ "name": "bulk_address_uploads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_bucket": {
+ "name": "s3_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_key": {
+ "name": "s3_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filename": {
+ "name": "filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready_for_processing'"
+ },
+ "source_headers": {
+ "name": "source_headers",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "column_mapping": {
+ "name": "column_mapping",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.aspect_condition": {
+ "name": "aspect_condition",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "element_id": {
+ "name": "element_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_type": {
+ "name": "aspect_type",
+ "type": "aspect_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_instance": {
+ "name": "aspect_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quantity": {
+ "name": "quantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "install_date": {
+ "name": "install_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "renewal_year": {
+ "name": "renewal_year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "comments": {
+ "name": "comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "aspect_condition_element_id_element_id_fk": {
+ "name": "aspect_condition_element_id_element_id_fk",
+ "tableFrom": "aspect_condition",
+ "tableTo": "element",
+ "columnsFrom": [
+ "element_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.element": {
+ "name": "element",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "survey_id": {
+ "name": "survey_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_type": {
+ "name": "element_type",
+ "type": "element_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_instance": {
+ "name": "element_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "element_survey_id_property_condition_survey_id_fk": {
+ "name": "element_survey_id_property_condition_survey_id_fk",
+ "tableFrom": "element",
+ "tableTo": "property_condition_survey",
+ "columnsFrom": [
+ "survey_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_condition_survey": {
+ "name": "property_condition_survey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_company_data": {
+ "name": "hubspot_company_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "company_name": {
+ "name": "company_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_deal_data": {
+ "name": "hubspot_deal_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deal_id": {
+ "name": "deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dealname": {
+ "name": "dealname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dealstage": {
+ "name": "dealstage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "project_code": {
+ "name": "project_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "listing_id": {
+ "name": "listing_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome_notes": {
+ "name": "outcome_notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_description": {
+ "name": "major_condition_issue_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_photos": {
+ "name": "major_condition_issue_photos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_evidence_s3_url": {
+ "name": "major_condition_issue_evidence_s3_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_status": {
+ "name": "coordination_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_status": {
+ "name": "design_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pashub_link": {
+ "name": "pashub_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sharepoint_link": {
+ "name": "sharepoint_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dampmould_growth": {
+ "name": "dampmould_growth",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pre_sap": {
+ "name": "pre_sap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordinator": {
+ "name": "coordinator",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_completion_date": {
+ "name": "mtp_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_re_model_completion_date": {
+ "name": "mtp_re_model_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ioe_v3_completion_date": {
+ "name": "ioe_v3_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_measures": {
+ "name": "proposed_measures",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approved_package": {
+ "name": "approved_package",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "designer": {
+ "name": "designer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_type": {
+ "name": "design_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_completion_date": {
+ "name": "design_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actual_measures_installed": {
+ "name": "actual_measures_installed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer": {
+ "name": "installer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer_handover": {
+ "name": "installer_handover",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_status": {
+ "name": "lodgement_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measures_lodgement_date": {
+ "name": "measures_lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_commencement_date": {
+ "name": "expected_commencement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_comments": {
+ "name": "coordination_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyor": {
+ "name": "surveyor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "damp_mould_and_repairs_comments": {
+ "name": "damp_mould_and_repairs_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_date": {
+ "name": "confirmed_survey_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_time": {
+ "name": "confirmed_survey_time",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyed_date": {
+ "name": "surveyed_date",
+ "type": "timestamp (6) with time zone",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_status_tracker": {
+ "name": "property_status_tracker",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "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 (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": {
+ "property_status_tracker_property_id_property_id_fk": {
+ "name": "property_status_tracker_property_id_property_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_status_tracker_portfolio_id_portfolio_id_fk": {
+ "name": "property_status_tracker_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_store": {
+ "name": "epc_store",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api_created_at": {
+ "name": "epc_api_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api": {
+ "name": "epc_api",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_created_at": {
+ "name": "epc_page_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page": {
+ "name": "epc_page",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_rrn": {
+ "name": "epc_page_rrn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_epc_store_uprn": {
+ "name": "uq_epc_store_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.files_from_surveyor": {
+ "name": "files_from_surveyor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_json_url": {
+ "name": "s3_json_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "files_from_surveyor_portfolio_id_portfolio_id_fk": {
+ "name": "files_from_surveyor_portfolio_id_portfolio_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "files_from_surveyor_property_id_property_id_fk": {
+ "name": "files_from_surveyor_property_id_property_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package": {
+ "name": "funding_package",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheme": {
+ "name": "scheme",
+ "type": "scheme",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "project_funding": {
+ "name": "project_funding",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_uplift": {
+ "name": "total_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "full_project_score": {
+ "name": "full_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_plan_id_plan_id_fk": {
+ "name": "funding_package_plan_id_plan_id_fk",
+ "tableFrom": "funding_package",
+ "tableTo": "plan",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package_measures": {
+ "name": "funding_package_measures",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "funding_package_id": {
+ "name": "funding_package_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure": {
+ "name": "measure",
+ "type": "type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "material_id": {
+ "name": "material_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "innovation_uplift": {
+ "name": "innovation_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_measures_funding_package_id_funding_package_id_fk": {
+ "name": "funding_package_measures_funding_package_id_funding_package_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "funding_package",
+ "columnsFrom": [
+ "funding_package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "funding_package_measures_material_id_material_id_fk": {
+ "name": "funding_package_measures_material_id_material_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "material",
+ "columnsFrom": [
+ "material_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.inspections": {
+ "name": "inspections",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "archetype": {
+ "name": "archetype",
+ "type": "inspection_archetype",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archetype_2": {
+ "name": "archetype_2",
+ "type": "inspection_archetype_2",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_construction": {
+ "name": "wall_construction",
+ "type": "inspections_wall_construction",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation": {
+ "name": "insulation",
+ "type": "inspections_wall_insulation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation_material": {
+ "name": "insulation_material",
+ "type": "inspections_insulation_material",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "borescoped": {
+ "name": "borescoped",
+ "type": "inspection_borescoped",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_orientation": {
+ "name": "roof_orientation",
+ "type": "inspections_roof_orientation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tile_hung": {
+ "name": "tile_hung",
+ "type": "inspections_tile_hung",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rendered": {
+ "name": "rendered",
+ "type": "inspections_rendered",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cladding": {
+ "name": "cladding",
+ "type": "inspections_cladding",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_issues": {
+ "name": "access_issues",
+ "type": "inspections_access_issues",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "surveyor_name": {
+ "name": "surveyor_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "inspections_property_id_property_id_fk": {
+ "name": "inspections_property_id_property_id_fk",
+ "tableFrom": "inspections",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.material": {
+ "name": "material",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "type",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_unit": {
+ "name": "cost_unit",
+ "type": "cost_unit",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "innovation_rate": {
+ "name": "innovation_rate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "size": {
+ "name": "size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "size_unit": {
+ "name": "size_unit",
+ "type": "size_unit",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "includes_scaffolding": {
+ "name": "includes_scaffolding",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "includes_battery": {
+ "name": "includes_battery",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "battery_size": {
+ "name": "battery_size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organisation": {
+ "name": "organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "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()"
+ },
+ "hubspot_company_id": {
+ "name": "hubspot_company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.portfolio_organisation": {
+ "name": "portfolio_organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organisation_id": {
+ "name": "organisation_id",
+ "type": "uuid",
+ "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": {
+ "portfolio_organisation_portfolio_id_portfolio_id_fk": {
+ "name": "portfolio_organisation_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "portfolio_organisation_organisation_id_organisation_id_fk": {
+ "name": "portfolio_organisation_organisation_id_organisation_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "organisation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_organisation_portfolio_id_unique": {
+ "name": "portfolio_organisation_portfolio_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.portfolio_capabilities": {
+ "name": "portfolio_capabilities",
+ "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
+ },
+ "capability": {
+ "name": "capability",
+ "type": "portfolio_capability",
+ "typeSchema": "public",
+ "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": {
+ "portfolio_capabilities_user_id_user_id_fk": {
+ "name": "portfolio_capabilities_user_id_user_id_fk",
+ "tableFrom": "portfolio_capabilities",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "portfolio_capabilities_portfolio_id_portfolio_id_fk": {
+ "name": "portfolio_capabilities_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolio_capabilities",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_capabilities_user_id_portfolio_id_capability_unique": {
+ "name": "portfolio_capabilities_user_id_portfolio_id_capability_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "portfolio_id",
+ "capability"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "building_reference_number": {
+ "name": "building_reference_number",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "installed_measures_sap_point_adjustment": {
+ "name": "installed_measures_sap_point_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_sap_points_adjusted_for_installed_measures": {
+ "name": "is_sap_points_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "original_sap_points": {
+ "name": "original_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_sap_points": {
+ "name": "lodged_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_epc_rating": {
+ "name": "lodged_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_portfolio_uprn": {
+ "name": "uq_property_portfolio_uprn",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"property\".\"uprn\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_expired": {
+ "name": "is_expired",
+ "type": "boolean",
+ "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
+ },
+ "sap_05_overwritten": {
+ "name": "sap_05_overwritten",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "sap_05_score": {
+ "name": "sap_05_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_05_epc_rating": {
+ "name": "sap_05_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": 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
+ },
+ "original_co2_emissions": {
+ "name": "original_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_primary_energy_consumption": {
+ "name": "original_primary_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand": {
+ "name": "original_current_energy_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand_heating_hotwater": {
+ "name": "original_current_energy_demand_heating_hotwater",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_co2_adjustment": {
+ "name": "installed_measures_co2_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_energy_demand_adjustment": {
+ "name": "installed_measures_energy_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_total_energy_bill_adjustment": {
+ "name": "installed_measures_total_energy_bill_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_heat_demand_adjustment": {
+ "name": "installed_measures_heat_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_epc_adjusted_for_installed_measures": {
+ "name": "is_epc_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "lodged_co2_emissions": {
+ "name": "lodged_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_heat_demand": {
+ "name": "lodged_heat_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_been_remodelled": {
+ "name": "has_been_remodelled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "environment_impact_current": {
+ "name": "environment_impact_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_details_epc_property_portfolio": {
+ "name": "uq_property_details_epc_property_portfolio",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "uq_property_details_spatial_uprn": {
+ "name": "uq_property_details_spatial_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.installed_measure": {
+ "name": "installed_measure",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_type": {
+ "name": "measure_type",
+ "type": "measure_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "installed_at": {
+ "name": "installed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "sap_points": {
+ "name": "sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "carbon_savings": {
+ "name": "carbon_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kwh_savings": {
+ "name": "kwh_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bill_savings": {
+ "name": "bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_demand_savings": {
+ "name": "heat_demand_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "idx_installed_measure_uprn": {
+ "name": "idx_installed_measure_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_active": {
+ "name": "idx_installed_measure_uprn_active",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_measure_type": {
+ "name": "idx_installed_measure_measure_type",
+ "columns": [
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_measure": {
+ "name": "idx_installed_measure_uprn_measure",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "post_sap_points": {
+ "name": "post_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_epc_rating": {
+ "name": "post_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_co2_emissions": {
+ "name": "post_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_savings": {
+ "name": "co2_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_bill": {
+ "name": "post_energy_bill",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_bill_savings": {
+ "name": "energy_bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_consumption": {
+ "name": "post_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_consumption_savings": {
+ "name": "energy_consumption_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_post_retrofit": {
+ "name": "valuation_post_retrofit",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_increase": {
+ "name": "valuation_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_of_works": {
+ "name": "cost_of_works",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contingency_cost": {
+ "name": "contingency_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plan_type": {
+ "name": "plan_type",
+ "type": "plan_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_plan_portfolio_scenario": {
+ "name": "idx_plan_portfolio_scenario",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_latest_per_property": {
+ "name": "idx_plan_latest_per_property",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "idx_plan_recommendations_plan_id": {
+ "name": "idx_plan_recommendations_plan_id",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_recommendations_plan_rec": {
+ "name": "idx_plan_recommendations_plan_rec",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "contingency_cost": {
+ "name": "contingency_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": {
+ "recommendation_property_id_idx": {
+ "name": "recommendation_property_id_idx",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_defaults": {
+ "name": "idx_recommendation_active_defaults",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_id_property": {
+ "name": "idx_recommendation_active_id_property",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": false
+ },
+ "quantity_unit": {
+ "name": "quantity_unit",
+ "type": "unit_quantity",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "recommendation_materials_recommendation_id_idx": {
+ "name": "recommendation_materials_recommendation_id_idx",
+ "columns": [
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": "cascade",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal_value": {
+ "name": "goal_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ashp_cop": {
+ "name": "ashp_cop",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2.8
+ },
+ "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
+ },
+ "contingency": {
+ "name": "contingency",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funding": {
+ "name": "funding",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sub_task": {
+ "name": "sub_task",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cloud_logs_url": {
+ "name": "cloud_logs_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sub_task_task_id_tasks_id_fk": {
+ "name": "sub_task_task_id_tasks_id_fk",
+ "tableFrom": "sub_task",
+ "tableTo": "tasks",
+ "columnsFrom": [
+ "task_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_source": {
+ "name": "task_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "service": {
+ "name": "service",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team": {
+ "name": "team",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "uuid",
+ "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": {
+ "team_org_id_organisation_id_fk": {
+ "name": "team_org_id_organisation_id_fk",
+ "tableFrom": "team",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "org_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_members": {
+ "name": "team_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "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": {
+ "team_members_user_id_user_id_fk": {
+ "name": "team_members_user_id_user_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_members_team_id_team_id_fk": {
+ "name": "team_members_team_id_team_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_portfolio_permissions": {
+ "name": "team_portfolio_permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "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": {
+ "team_portfolio_permissions_team_id_team_id_fk": {
+ "name": "team_portfolio_permissions_team_id_team_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_portfolio_permissions_portfolio_id_portfolio_id_fk": {
+ "name": "team_portfolio_permissions_portfolio_id_portfolio_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.uploaded_files": {
+ "name": "uploaded_files",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "s3_file_bucket": {
+ "name": "s3_file_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_file_key": {
+ "name": "s3_file_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_upload_timestamp": {
+ "name": "s3_upload_timestamp",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_listing_id": {
+ "name": "hubspot_listing_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_type": {
+ "name": "file_type",
+ "type": "file_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_source": {
+ "name": "file_source",
+ "type": "file_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measure_name": {
+ "name": "measure_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploaded_files_uploaded_by_user_id_fk": {
+ "name": "uploaded_files_uploaded_by_user_id_fk",
+ "tableFrom": "uploaded_files",
+ "tableTo": "user",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_id": {
+ "name": "oauth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_provider": {
+ "name": "oauth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "onboarded": {
+ "name": "onboarded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "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": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_profiles": {
+ "name": "user_profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_type": {
+ "name": "user_type",
+ "type": "user_profiles_user_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_count": {
+ "name": "property_count",
+ "type": "user_profiles_property_count",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goals": {
+ "name": "goals",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referral_source": {
+ "name": "referral_source",
+ "type": "user_profiles_referral_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nrla_membership_id": {
+ "name": "nrla_membership_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accepted_privacy": {
+ "name": "accepted_privacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accepted_privacy_at": {
+ "name": "accepted_privacy_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "marketing_opt_in": {
+ "name": "marketing_opt_in",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "marketing_opt_in_at": {
+ "name": "marketing_opt_in_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_name": {
+ "name": "last_name",
+ "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": {
+ "user_profiles_user_id_user_id_fk": {
+ "name": "user_profiles_user_id_user_id_fk",
+ "tableFrom": "user_profiles",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verificationToken": {
+ "name": "verificationToken",
+ "schema": "",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": [
+ "identifier",
+ "token"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.whlg": {
+ "name": "whlg",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.aspect_type": {
+ "name": "aspect_type",
+ "schema": "public",
+ "values": [
+ "material",
+ "condition",
+ "type",
+ "area",
+ "configuration",
+ "presence",
+ "risk",
+ "severity",
+ "location",
+ "finish",
+ "insulation",
+ "pointing",
+ "spalling",
+ "lintels",
+ "cladding",
+ "category",
+ "quantity",
+ "adequacy",
+ "rating",
+ "strategy",
+ "extent",
+ "distribution",
+ "structure",
+ "covering",
+ "fire_rating",
+ "external_decoration",
+ "work_required",
+ "age_band",
+ "construction_type",
+ "classification",
+ "system"
+ ]
+ },
+ "public.element_type": {
+ "name": "element_type",
+ "schema": "public",
+ "values": [
+ "property",
+ "property_construction_type",
+ "property_classification",
+ "property_age_band",
+ "storey_count",
+ "floor_level",
+ "floor_level_front_door",
+ "accessible_housing_register",
+ "asbestos",
+ "quality_standard",
+ "ccu",
+ "passenger_lift",
+ "stairlift",
+ "disabled_hoist_tracking",
+ "disabled_facilities",
+ "steps_to_front_door",
+ "roof",
+ "pitched_roof_covering",
+ "flat_roof_covering",
+ "rainwater_goods",
+ "loft_insulation",
+ "porch_canopy",
+ "chimney",
+ "fascia",
+ "soffit",
+ "fascia_soffit_bargeboards",
+ "gutters",
+ "store_roof",
+ "garage_roof",
+ "garage_and_store_roof",
+ "external_wall",
+ "external_noise_insulation",
+ "primary_wall",
+ "secondary_wall",
+ "downpipes",
+ "external_decoration",
+ "cladding",
+ "spandrel_panels",
+ "garage_walls",
+ "party_wall_fire_break",
+ "external_brickwork_pointing",
+ "internal_downpipes_external_area",
+ "external_windows",
+ "communal_windows",
+ "secondary_glazing",
+ "store_windows",
+ "garage_windows",
+ "garage_and_store_windows",
+ "external_door",
+ "front_door",
+ "rear_door",
+ "store_door",
+ "garage_door",
+ "garage_and_store_door",
+ "communal_entrance_door",
+ "main_door",
+ "block_entrance_door",
+ "lintel",
+ "patio_french_door",
+ "door_entry_handset",
+ "paths_and_hardstandings",
+ "parking_areas",
+ "boundary_walls",
+ "front_fencing",
+ "rear_fencing",
+ "side_fencing",
+ "rear_gate",
+ "front_gate",
+ "gates",
+ "retaining_walls",
+ "private_balcony",
+ "balcony_balustrade",
+ "outbuildings",
+ "garage_structure",
+ "paving",
+ "roads",
+ "soil_and_vent",
+ "solar_thermals",
+ "drop_kerb",
+ "outbuilding_overhaul",
+ "external_structural_defects",
+ "access_ramp",
+ "kitchen",
+ "kitchen_space_layout",
+ "tenant_installed_kitchen",
+ "kitchen_extractor_fan",
+ "bathroom",
+ "secondary_bathroom",
+ "secondary_toilet",
+ "bathroom_extractor_fan",
+ "additional_wc_or_whb",
+ "bathroom_remaining_life_source",
+ "kitchen_remaining_life_source",
+ "central_heating",
+ "heating_boiler",
+ "heating_distribution",
+ "secondary_heating",
+ "hot_water_system",
+ "cold_water_storage",
+ "heating_system",
+ "boiler_fuel",
+ "water_heating",
+ "programmable_heating",
+ "community_heating",
+ "gas_available",
+ "heat_recovery_units",
+ "heating_improvements",
+ "electrical_wiring",
+ "consumer_unit",
+ "smoke_detection",
+ "heat_detection",
+ "carbon_monoxide_detection",
+ "fire_door_rating",
+ "fire_risk_assessment",
+ "internal_wiring",
+ "electrics",
+ "communal_heating",
+ "communal_boiler",
+ "communal_electrics",
+ "communal_fire_alarm",
+ "communal_emergency_lighting",
+ "communal_door_entry",
+ "communal_cctv",
+ "communal_bin_store",
+ "communal_bin_store_doors",
+ "communal_bin_store_walls",
+ "communal_bin_store_roof",
+ "communal_refuse_chute",
+ "communal_floor_covering",
+ "communal_kitchen",
+ "communal_bathroom",
+ "communal_toilets",
+ "communal_gates",
+ "communal_lift",
+ "communal_passenger_lift",
+ "communal_balcony_walkway",
+ "communal_entrance",
+ "communal_internal_decorations",
+ "communal_internal_floor",
+ "communal_walkways",
+ "communal_external_doors",
+ "communal_stairs",
+ "communal_aerial",
+ "communal_aov",
+ "communal_internal_doors",
+ "communal_lateral_mains",
+ "communal_lighting",
+ "communal_lighting_conductor",
+ "communal_store_roof",
+ "communal_store_walls",
+ "communal_store_doors",
+ "communal_warden_call_system",
+ "communal_bms",
+ "communal_booster_pump",
+ "communal_dry_riser",
+ "communal_wet_riser",
+ "communal_cold_water_storage",
+ "communal_sprinkler",
+ "communal_plug_sockets",
+ "communal_circulation_space",
+ "ffhh_damp",
+ "ffhh_hold_and_cold_water",
+ "ffhh_drainage_lavatories",
+ "ffhh_neglected",
+ "ffhh_natural_light",
+ "ffhh_ventilation",
+ "ffhh_food_prep_and_washup",
+ "ffhh_unsafe_layout",
+ "ffhh_unstable_building",
+ "hhsrs_damp_and_mould",
+ "hhsrs_excess_cold",
+ "hhsrs_excess_heat",
+ "hhsrs_asbestos_and_mmf",
+ "hhsrs_biocides",
+ "hhsrs_carbon_monoxide",
+ "hhsrs_lead",
+ "hhsrs_radiation",
+ "hhsrs_uncombusted_fuel_gas",
+ "hhsrs_volatile_organic_compounds",
+ "hhsrs_crowding_and_space",
+ "hhsrs_entry_by_intruders",
+ "hhsrs_lighting",
+ "hhsrs_noise",
+ "hhsrs_domestic_hygiene_pests_refuse",
+ "hhsrs_food_safety",
+ "hhsrs_personal_hygiene_sanitation",
+ "hhsrs_water_supply",
+ "hhsrs_falls_associated_with_baths",
+ "hhsrs_falls_on_level_surfaces",
+ "hhsrs_falls_on_stairs",
+ "hhsrs_falls_between_levels",
+ "hhsrs_electrical_hazards",
+ "hhsrs_fire",
+ "hhsrs_flames_hot_surfaces",
+ "hhsrs_collision_and_entrapment",
+ "hhsrs_collision_hazards_low_headroom",
+ "hhsrs_explosions",
+ "hhsrs_ergonomics",
+ "hhsrs_structural_collapse",
+ "hhsrs_amenities"
+ ]
+ },
+ "public.document_type": {
+ "name": "document_type",
+ "schema": "public",
+ "values": [
+ "EPR",
+ "Condition Report",
+ "Evidence Report",
+ "Summary Information",
+ "Floor Plan",
+ "Scenario Draft EPC",
+ "Scenario Site Notes"
+ ]
+ },
+ "public.scheme": {
+ "name": "scheme",
+ "schema": "public",
+ "values": [
+ "eco4",
+ "gbis",
+ "whlg",
+ "none"
+ ]
+ },
+ "public.inspection_archetype_2": {
+ "name": "inspection_archetype_2",
+ "schema": "public",
+ "values": [
+ "detached",
+ "mid-terrace",
+ "enclosed mid-terrace",
+ "end-terrace",
+ "enclosed end-terrace",
+ "semi-detached"
+ ]
+ },
+ "public.inspection_archetype": {
+ "name": "inspection_archetype",
+ "schema": "public",
+ "values": [
+ "Bungalow",
+ "Flat",
+ "Maisonette",
+ "House",
+ "non-domestic"
+ ]
+ },
+ "public.inspection_borescoped": {
+ "name": "inspection_borescoped",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "refused"
+ ]
+ },
+ "public.inspections_access_issues": {
+ "name": "inspections_access_issues",
+ "schema": "public",
+ "values": [
+ "see notes",
+ "damp issues",
+ "foliage on walls",
+ "bushes against wall",
+ "trees around/anove property",
+ "high rise block flats/maisonettes",
+ "conservatory",
+ "lean-to",
+ "garage",
+ "extension",
+ "decking",
+ "shed against wall"
+ ]
+ },
+ "public.inspections_cladding": {
+ "name": "inspections_cladding",
+ "schema": "public",
+ "values": [
+ "none",
+ "cladded with “sufficient space to fill the wall”",
+ "cladded with “insufficient space to fill the wall”"
+ ]
+ },
+ "public.inspections_insulation_material": {
+ "name": "inspections_insulation_material",
+ "schema": "public",
+ "values": [
+ "empty 50-90",
+ "empty 100+",
+ "empty 30-40",
+ "empty less than 30",
+ "loose fibre/wool",
+ "eps/celo/king",
+ "fibre batts - with cavity",
+ "fibre batts - no cavity",
+ "loose bead",
+ "glued bead",
+ "formaldehyde",
+ "bubble wrap",
+ "poly chunks"
+ ]
+ },
+ "public.inspections_rendered": {
+ "name": "inspections_rendered",
+ "schema": "public",
+ "values": [
+ "no render",
+ "rendered with “insufficient” space between dpc and render",
+ "rendered with “sufficient” space between dpc and render"
+ ]
+ },
+ "public.inspections_roof_orientation": {
+ "name": "inspections_roof_orientation",
+ "schema": "public",
+ "values": [
+ "north",
+ "east",
+ "south",
+ "west",
+ "north-east",
+ "north-west",
+ "south-east",
+ "south-west",
+ "n/s split",
+ "e/w split",
+ "ne/sw split",
+ "nw/se split",
+ "flat roof",
+ "no roof",
+ "roof too small",
+ "already has solar pv"
+ ]
+ },
+ "public.inspections_tile_hung": {
+ "name": "inspections_tile_hung",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "first floor flats are tile hung"
+ ]
+ },
+ "public.inspections_wall_construction": {
+ "name": "inspections_wall_construction",
+ "schema": "public",
+ "values": [
+ "cavity",
+ "solid",
+ "system built",
+ "timber framed",
+ "steel framed",
+ "re-walled cavity",
+ "mansard pre-fab",
+ "mansard ewi",
+ "mansard re-walled"
+ ]
+ },
+ "public.inspections_wall_insulation": {
+ "name": "inspections_wall_insulation",
+ "schema": "public",
+ "values": [
+ "empty cavity",
+ "filled at build",
+ "partial",
+ "retro drilled",
+ "ewi",
+ "iwi",
+ "solid non-cavity",
+ "system built",
+ "timber framed",
+ "steel framed"
+ ]
+ },
+ "public.cost_unit": {
+ "name": "cost_unit",
+ "schema": "public",
+ "values": [
+ "gbp_sq_meter",
+ "gbp_per_unit",
+ "gbp_per_m2",
+ "gbp_per_m"
+ ]
+ },
+ "public.depth_unit": {
+ "name": "depth_unit",
+ "schema": "public",
+ "values": [
+ "mm"
+ ]
+ },
+ "public.type": {
+ "name": "type",
+ "schema": "public",
+ "values": [
+ "suspended_floor_insulation",
+ "solid_floor_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "cavity_wall_insulation",
+ "mechanical_ventilation",
+ "loft_insulation",
+ "exposed_floor_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "cavity_wall_extraction",
+ "iwi_wall_demolition",
+ "iwi_vapour_barrier",
+ "iwi_redecoration",
+ "suspended_floor_demolition",
+ "suspended_floor_redecoration",
+ "suspended_floor_vapour_barrier",
+ "solid_floor_demolition",
+ "solid_floor_preparation",
+ "solid_floor_vapour_barrier",
+ "solid_floor_redecoration",
+ "ewi_wall_demolition",
+ "ewi_wall_preparation",
+ "ewi_wall_redecoration",
+ "low_energy_lighting_installation",
+ "flat_roof_preparation",
+ "flat_roof_vapour_barrier",
+ "flat_roof_waterproofing",
+ "windows_glazing",
+ "secondary_glazing",
+ "double_glazing",
+ "trickle_vent",
+ "door_undercut",
+ "solar_pv",
+ "solar_battery",
+ "scaffolding",
+ "high_heat_retention_storage_heaters",
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "sealing_fireplace"
+ ]
+ },
+ "public.r_value_unit": {
+ "name": "r_value_unit",
+ "schema": "public",
+ "values": [
+ "square_meter_kelvin_per_watt"
+ ]
+ },
+ "public.size_unit": {
+ "name": "size_unit",
+ "schema": "public",
+ "values": [
+ "kWp",
+ "kW",
+ "watt",
+ "storey"
+ ]
+ },
+ "public.thermal_conductivity_unit": {
+ "name": "thermal_conductivity_unit",
+ "schema": "public",
+ "values": [
+ "watt_per_meter_kelvin"
+ ]
+ },
+ "public.goal": {
+ "name": "goal",
+ "schema": "public",
+ "values": [
+ "Valuation Improvement",
+ "Increasing EPC",
+ "Reducing CO2 emissions",
+ "Energy Savings",
+ "None"
+ ]
+ },
+ "public.portfolio_capability": {
+ "name": "portfolio_capability",
+ "schema": "public",
+ "values": [
+ "approver",
+ "contractor"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "creator",
+ "admin",
+ "read",
+ "write"
+ ]
+ },
+ "public.status": {
+ "name": "status",
+ "schema": "public",
+ "values": [
+ "scoping",
+ "survey",
+ "assessment",
+ "tendering",
+ "project underway",
+ "completion; status: on track",
+ "completion; status: delayed",
+ "completion; status: at risk",
+ "completion; status: completed",
+ "needs review"
+ ]
+ },
+ "public.epc": {
+ "name": "epc",
+ "schema": "public",
+ "values": [
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G"
+ ]
+ },
+ "public.creation_status": {
+ "name": "creation_status",
+ "schema": "public",
+ "values": [
+ "LOADING",
+ "READY",
+ "ERROR"
+ ]
+ },
+ "public.housing_type": {
+ "name": "housing_type",
+ "schema": "public",
+ "values": [
+ "Private",
+ "Social"
+ ]
+ },
+ "public.measure_type": {
+ "name": "measure_type",
+ "schema": "public",
+ "values": [
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "high_heat_retention_storage_heaters",
+ "secondary_heating",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "cylinder_thermostat",
+ "cavity_wall_insulation",
+ "extension_cavity_wall_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "loft_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "solid_floor_insulation",
+ "suspended_floor_insulation",
+ "double_glazing",
+ "secondary_glazing",
+ "draught_proofing",
+ "mechanical_ventilation",
+ "low_energy_lighting",
+ "solar_pv",
+ "hot_water_tank_insulation",
+ "sealing_open_fireplace",
+ "damp_mould",
+ "door_undercut",
+ "extractor_fan",
+ "loft_board",
+ "trickle_vent"
+ ]
+ },
+ "public.plan_type": {
+ "name": "plan_type",
+ "schema": "public",
+ "values": [
+ "solar_eco4",
+ "solar_hhrsh_eco4",
+ "empty_cavity_eco",
+ "partial_cavity_eco",
+ "extraction_eco"
+ ]
+ },
+ "public.unit_quantity": {
+ "name": "unit_quantity",
+ "schema": "public",
+ "values": [
+ "m2",
+ "part",
+ "kwp"
+ ]
+ },
+ "public.scenario_type": {
+ "name": "scenario_type",
+ "schema": "public",
+ "values": [
+ "unit",
+ "building"
+ ]
+ },
+ "public.source": {
+ "name": "source",
+ "schema": "public",
+ "values": [
+ "portfolio_id"
+ ]
+ },
+ "public.file_source": {
+ "name": "file_source",
+ "schema": "public",
+ "values": [
+ "pas hub",
+ "sharepoint",
+ "hubspot",
+ "ecmk",
+ "contractor"
+ ]
+ },
+ "public.file_type": {
+ "name": "file_type",
+ "schema": "public",
+ "values": [
+ "photo_pack",
+ "site_note",
+ "rd_sap_site_note",
+ "pas_2023_ventilation",
+ "pas_2023_condition",
+ "pas_significance",
+ "par_photo_pack",
+ "pas_2023_property",
+ "pas_2023_occupancy",
+ "ecmk_site_note",
+ "ecmk_rd_sap_site_note",
+ "ecmk_survey_xml",
+ "pre_photo",
+ "mid_photo",
+ "post_photo",
+ "pre_installation_building_inspection",
+ "claim_of_compliance",
+ "handover_pack",
+ "insurance_guarantee",
+ "installer_qualifications",
+ "mcs_compliance_certificate",
+ "minor_works_electrical_certificate",
+ "point_of_work_risk_assessment",
+ "installer_feedback",
+ "workmanship_warranty",
+ "g98_notification",
+ "certificate_of_conformity",
+ "ventilation_assessment_checklist",
+ "contractor_other"
+ ]
+ },
+ "public.user_profiles_property_count": {
+ "name": "user_profiles_property_count",
+ "schema": "public",
+ "values": [
+ "1",
+ "2–5",
+ "6–20",
+ "21+",
+ "1–50",
+ "51–100",
+ "101–300",
+ "301–1000",
+ "1000+"
+ ]
+ },
+ "public.user_profiles_referral_source": {
+ "name": "user_profiles_referral_source",
+ "schema": "public",
+ "values": [
+ "search",
+ "social_media",
+ "NRLA",
+ "partner",
+ "word_of_mouth",
+ "other"
+ ]
+ },
+ "public.user_profiles_user_type": {
+ "name": "user_profiles_user_type",
+ "schema": "public",
+ "values": [
+ "private_landlord",
+ "private_tenant",
+ "social_landlord",
+ "social_tenant",
+ "homeowner",
+ "other"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ 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 bb740c26..89e4930a 100644
--- a/src/app/db/migrations/meta/_journal.json
+++ b/src/app/db/migrations/meta/_journal.json
@@ -1205,6 +1205,13 @@
"when": 1776361262258,
"tag": "0171_chunky_wallow",
"breakpoints": true
+ },
+ {
+ "idx": 172,
+ "version": "7",
+ "when": 1776374085626,
+ "tag": "0172_kind_eddie_brock",
+ "breakpoints": true
}
]
-}
+}
\ No newline at end of file
From ea50699ee31c72ba74dfbe0c8f179ff7211d71e9 Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Thu, 16 Apr 2026 21:26:21 +0000
Subject: [PATCH 08/43] temp commenting out bulkupload modal
---
src/app/components/portfolio/AddNew.tsx | 186 ++++++++++++------------
1 file changed, 93 insertions(+), 93 deletions(-)
diff --git a/src/app/components/portfolio/AddNew.tsx b/src/app/components/portfolio/AddNew.tsx
index 88595073..28bcec7b 100644
--- a/src/app/components/portfolio/AddNew.tsx
+++ b/src/app/components/portfolio/AddNew.tsx
@@ -13,7 +13,7 @@ import {
import { cn } from "@/lib/utils";
import { useRouter } from "next/navigation";
import { Dispatch, SetStateAction, useState } from "react";
-import BulkUploadComingSoonModal from "@/app/components/portfolio/BulkUploadComingSoonModal";
+// import BulkUploadComingSoonModal from "@/app/components/portfolio/BulkUploadComingSoonModal";
interface AddNewProps {
portfolioId: string;
@@ -37,112 +37,112 @@ export default function AddNew({
return (
<>
- setIsBulkUploadOpen(false)}
portfolioId={portfolioId}
- />
-
>
);
}
From 881364b5627013a51d80597086061970363ce126 Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Fri, 17 Apr 2026 00:01:08 +0000
Subject: [PATCH 09/43] migrated db and building out upload doc ui and approval
log
---
.../live-tracking/property-documents/route.ts | 4 +-
.../[portfolioId]/approvals/route.ts | 168 +-
.../api/upload/contractor-install/route.ts | 61 +-
src/app/db/migrations/0173_neat_bastion.sql | 26 +
src/app/db/migrations/meta/0173_snapshot.json | 6754 +++++++++++++++++
src/app/db/migrations/meta/_journal.json | 7 +
src/app/db/schema/approvals.ts | 49 +-
.../settings/UsersPermissionsCard.tsx | 32 +-
.../[slug]/(portfolio)/settings/roles.tsx | 4 +-
.../live/ApprovalConfirmDialog.tsx | 140 +
.../live/ContractorUploadModal.tsx | 569 ++
.../your-projects/live/DocumentTable.tsx | 25 +-
.../live/DocumentTableColumns.tsx | 22 +-
.../your-projects/live/InstallUploadModal.tsx | 272 -
.../your-projects/live/LiveTracker.tsx | 3 +
.../your-projects/live/MeasuresTable.tsx | 378 +-
.../live/PropertyDetailDrawer.tsx | 110 +-
.../(portfolio)/your-projects/live/page.tsx | 36 +-
18 files changed, 8111 insertions(+), 549 deletions(-)
create mode 100644 src/app/db/migrations/0173_neat_bastion.sql
create mode 100644 src/app/db/migrations/meta/0173_snapshot.json
create mode 100644 src/app/portfolio/[slug]/(portfolio)/your-projects/live/ApprovalConfirmDialog.tsx
create mode 100644 src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
delete mode 100644 src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx
diff --git a/src/app/api/live-tracking/property-documents/route.ts b/src/app/api/live-tracking/property-documents/route.ts
index 912dba8e..0e73dc6d 100644
--- a/src/app/api/live-tracking/property-documents/route.ts
+++ b/src/app/api/live-tracking/property-documents/route.ts
@@ -29,6 +29,7 @@ export async function GET(req: Request) {
s3FileBucket: uploadedFiles.s3FileBucket,
s3UploadTimestamp: uploadedFiles.s3UploadTimestamp,
fileType: uploadedFiles.fileType,
+ source: uploadedFiles.source,
uprn: uploadedFiles.uprn,
landlordPropertyId: uploadedFiles.landlordPropertyId,
})
@@ -39,7 +40,8 @@ export async function GET(req: Request) {
id: String(row.id),
s3FileKey: row.s3FileKey,
s3FileBucket: row.s3FileBucket,
- docType: row.fileType ?? "unknown",
+ docType: row.fileType ?? null,
+ source: row.source ?? null,
s3UploadTimestamp: row.s3UploadTimestamp.toISOString(),
uprn: row.uprn !== null ? String(row.uprn) : null,
landlordPropertyId: row.landlordPropertyId,
diff --git a/src/app/api/portfolio/[portfolioId]/approvals/route.ts b/src/app/api/portfolio/[portfolioId]/approvals/route.ts
index 4ca7dac1..c4b39333 100644
--- a/src/app/api/portfolio/[portfolioId]/approvals/route.ts
+++ b/src/app/api/portfolio/[portfolioId]/approvals/route.ts
@@ -1,12 +1,12 @@
import { db } from "@/app/db/db";
import { NextRequest, NextResponse } from "next/server";
-import { dealApprovals, dealApprovedMeasures } from "@/app/db/schema/approvals";
import {
- portfolioCapabilities,
- portfolioUsers,
-} from "@/app/db/schema/portfolio";
+ dealMeasureApprovals,
+ dealMeasureApprovalEvents,
+} from "@/app/db/schema/approvals";
+import { portfolioCapabilities } from "@/app/db/schema/portfolio";
import { user } from "@/app/db/schema/users";
-import { and, eq, inArray } from "drizzle-orm";
+import { and, eq, inArray, sql } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
@@ -38,62 +38,82 @@ async function hasApproverCapability(
return rows.length > 0;
}
-// GET — return all approved measures grouped by hubspot deal id
-// Query param: dealIds (comma-separated)
+// GET — return currently approved measures per deal, and optionally the audit event log
+// Query params:
+// dealIds comma-separated HubSpot deal IDs (required)
+// include "events" to also return the audit log
export async function GET(
req: NextRequest,
props: { params: Promise<{ portfolioId: string }> },
) {
- const { portfolioId } = await props.params;
const url = new URL(req.url);
const dealIdsParam = url.searchParams.get("dealIds");
+ const includeEvents = url.searchParams.get("include") === "events";
if (!dealIdsParam) {
- return NextResponse.json({});
+ return NextResponse.json(includeEvents ? { approved: {}, events: [] } : {});
}
const dealIds = dealIdsParam.split(",").filter(Boolean);
if (dealIds.length === 0) {
- return NextResponse.json({});
+ return NextResponse.json(includeEvents ? { approved: {}, events: [] } : {});
}
try {
- // Fetch most recent approval per deal + its measures
- const approvals = await db
+ // Current approved measures
+ const approvalRows = await db
.select({
- id: dealApprovals.id,
- hubspotDealId: dealApprovals.hubspotDealId,
- approvedAt: dealApprovals.approvedAt,
+ hubspotDealId: dealMeasureApprovals.hubspotDealId,
+ measureName: dealMeasureApprovals.measureName,
+ approvedByEmail: user.email,
+ approvedByName: user.firstName,
+ approvedAt: dealMeasureApprovals.approvedAt,
})
- .from(dealApprovals)
- .where(inArray(dealApprovals.hubspotDealId, dealIds));
+ .from(dealMeasureApprovals)
+ .leftJoin(user, eq(user.id, dealMeasureApprovals.approvedBy))
+ .where(
+ and(
+ inArray(dealMeasureApprovals.hubspotDealId, dealIds),
+ eq(dealMeasureApprovals.isApproved, true),
+ ),
+ );
- if (approvals.length === 0) {
- return NextResponse.json({});
+ const approved: Record = {};
+ for (const row of approvalRows) {
+ (approved[row.hubspotDealId] ??= []).push(row.measureName);
}
- const approvalIds = approvals.map((a) => a.id);
- const measures = await db
- .select({
- dealApprovalId: dealApprovedMeasures.dealApprovalId,
- measureName: dealApprovedMeasures.measureName,
- })
- .from(dealApprovedMeasures)
- .where(inArray(dealApprovedMeasures.dealApprovalId, approvalIds));
-
- // Build map: dealId -> approved measure names[]
- const approvalById = new Map(approvals.map((a) => [a.id.toString(), a]));
- const result: Record = {};
-
- for (const m of measures) {
- const approval = approvalById.get(m.dealApprovalId.toString());
- if (!approval) continue;
- const dealId = approval.hubspotDealId;
- if (!result[dealId]) result[dealId] = [];
- result[dealId].push(m.measureName);
+ if (!includeEvents) {
+ return NextResponse.json(approved);
}
- return NextResponse.json(result);
+ // Audit event log
+ const eventRows = await db
+ .select({
+ id: dealMeasureApprovalEvents.id,
+ hubspotDealId: dealMeasureApprovalEvents.hubspotDealId,
+ measureName: dealMeasureApprovalEvents.measureName,
+ action: dealMeasureApprovalEvents.action,
+ actedByEmail: user.email,
+ actedByName: user.firstName,
+ actedAt: dealMeasureApprovalEvents.actedAt,
+ })
+ .from(dealMeasureApprovalEvents)
+ .leftJoin(user, eq(user.id, dealMeasureApprovalEvents.actedBy))
+ .where(inArray(dealMeasureApprovalEvents.hubspotDealId, dealIds))
+ .orderBy(dealMeasureApprovalEvents.actedAt);
+
+ const events = eventRows.map((e) => ({
+ id: e.id.toString(),
+ hubspotDealId: e.hubspotDealId,
+ measureName: e.measureName,
+ action: e.action,
+ actedByEmail: e.actedByEmail ?? "",
+ actedByName: e.actedByName ?? null,
+ actedAt: e.actedAt.toISOString(),
+ }));
+
+ return NextResponse.json({ approved, events });
} catch (err) {
console.error("GET /approvals error:", err);
return NextResponse.json(
@@ -103,7 +123,8 @@ export async function GET(
}
}
-// POST — save approvals for one or more deals (replaces previous approvals)
+// POST — apply explicit approve/unapprove changes, updating current state + audit log
+// Body: { changes: [{ hubspotDealId, measureName, approved: boolean }] }
export async function POST(
req: NextRequest,
props: { params: Promise<{ portfolioId: string }> },
@@ -127,10 +148,11 @@ export async function POST(
}
const bodySchema = z.object({
- deals: z.array(
+ changes: z.array(
z.object({
hubspotDealId: z.string(),
- approvedMeasures: z.array(z.string()),
+ measureName: z.string(),
+ approved: z.boolean(),
}),
),
});
@@ -142,42 +164,44 @@ export async function POST(
return NextResponse.json({ error: "Invalid body" }, { status: 400 });
}
+ if (body.changes.length === 0) {
+ return NextResponse.json({ success: true });
+ }
+
try {
- for (const deal of body.deals) {
- // Delete previous approvals for this deal (replace approach)
- const existing = await db
- .select({ id: dealApprovals.id })
- .from(dealApprovals)
- .where(eq(dealApprovals.hubspotDealId, deal.hubspotDealId));
+ const now = new Date();
- if (existing.length > 0) {
- const existingIds = existing.map((e) => e.id);
- await db
- .delete(dealApprovedMeasures)
- .where(inArray(dealApprovedMeasures.dealApprovalId, existingIds));
- await db
- .delete(dealApprovals)
- .where(inArray(dealApprovals.id, existingIds));
- }
-
- if (deal.approvedMeasures.length === 0) continue;
-
- // Insert new approval session
- const [approval] = await db
- .insert(dealApprovals)
+ for (const change of body.changes) {
+ // 1. Upsert current state
+ await db
+ .insert(dealMeasureApprovals)
.values({
- hubspotDealId: deal.hubspotDealId,
+ hubspotDealId: change.hubspotDealId,
+ measureName: change.measureName,
+ isApproved: change.approved,
approvedBy: userId,
+ approvedAt: now,
})
- .returning({ id: dealApprovals.id });
+ .onConflictDoUpdate({
+ target: [
+ dealMeasureApprovals.hubspotDealId,
+ dealMeasureApprovals.measureName,
+ ],
+ set: {
+ isApproved: change.approved,
+ approvedBy: userId,
+ approvedAt: now,
+ },
+ });
- // Insert individual approved measures (stored as free text matching HubSpot proposedMeasures)
- await db.insert(dealApprovedMeasures).values(
- deal.approvedMeasures.map((m) => ({
- dealApprovalId: approval.id,
- measureName: m,
- })),
- );
+ // 2. Append to audit log
+ await db.insert(dealMeasureApprovalEvents).values({
+ hubspotDealId: change.hubspotDealId,
+ measureName: change.measureName,
+ action: change.approved ? "approved" : "unapproved",
+ actedBy: userId,
+ actedAt: now,
+ });
}
return NextResponse.json({ success: true });
diff --git a/src/app/api/upload/contractor-install/route.ts b/src/app/api/upload/contractor-install/route.ts
index ea05d4bc..c3c3fa0f 100644
--- a/src/app/api/upload/contractor-install/route.ts
+++ b/src/app/api/upload/contractor-install/route.ts
@@ -2,12 +2,12 @@ import { db } from "@/app/db/db";
import { NextRequest, NextResponse } from "next/server";
import { uploadedFiles } from "@/app/db/schema/uploaded_files";
import { user } from "@/app/db/schema/users";
-import { eq } from "drizzle-orm";
+import { eq, inArray } from "drizzle-orm";
import { z } from "zod";
import { getServerSession } from "next-auth";
import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
-// POST — record a contractor install document in uploaded_files
+// POST — record a contractor install document in uploaded_files (fileType optional — can be classified later)
export async function POST(req: NextRequest) {
const session = await getServerSession(AuthOptions);
if (!session?.user?.email) {
@@ -17,7 +17,7 @@ export async function POST(req: NextRequest) {
const bodySchema = z.object({
s3FileKey: z.string(),
s3FileBucket: z.string(),
- fileType: z.string(),
+ fileType: z.string().optional(), // optional — null means unclassified
measureName: z.string().optional(),
uprn: z.string().optional(),
hubspotDealId: z.string().optional(),
@@ -32,7 +32,6 @@ export async function POST(req: NextRequest) {
}
try {
- // Resolve uploader's user ID
const userRow = await db
.select({ id: user.id })
.from(user)
@@ -47,7 +46,7 @@ export async function POST(req: NextRequest) {
s3FileBucket: body.s3FileBucket,
s3FileKey: body.s3FileKey,
s3UploadTimestamp: new Date(),
- fileType: body.fileType as any,
+ fileType: (body.fileType as any) ?? null,
source: "contractor",
measureName: body.measureName ?? null,
uploadedBy: uploadedBy ?? undefined,
@@ -60,9 +59,53 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ id: inserted.id.toString() }, { status: 201 });
} catch (err) {
console.error("POST /upload/contractor-install error:", err);
- return NextResponse.json(
- { error: "Failed to record upload" },
- { status: 500 },
- );
+ return NextResponse.json({ error: "Failed to record upload" }, { status: 500 });
+ }
+}
+
+// PATCH — update fileType and measureName for previously unclassified uploads
+export async function PATCH(req: NextRequest) {
+ const session = await getServerSession(AuthOptions);
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
+ }
+
+ const bodySchema = z.object({
+ updates: z.array(
+ z.object({
+ id: z.string(),
+ fileType: z.string(),
+ measureName: z.string().optional(),
+ }),
+ ),
+ });
+
+ let body: z.infer;
+ try {
+ body = bodySchema.parse(await req.json());
+ } catch {
+ return NextResponse.json({ error: "Invalid body" }, { status: 400 });
+ }
+
+ if (body.updates.length === 0) {
+ return NextResponse.json({ success: true });
+ }
+
+ try {
+ // Update each record individually (small batches — no bulk update without raw SQL)
+ for (const update of body.updates) {
+ await db
+ .update(uploadedFiles)
+ .set({
+ fileType: update.fileType as any,
+ measureName: update.measureName ?? null,
+ })
+ .where(eq(uploadedFiles.id, BigInt(update.id)));
+ }
+
+ return NextResponse.json({ success: true });
+ } catch (err) {
+ console.error("PATCH /upload/contractor-install error:", err);
+ return NextResponse.json({ error: "Failed to update classifications" }, { status: 500 });
}
}
diff --git a/src/app/db/migrations/0173_neat_bastion.sql b/src/app/db/migrations/0173_neat_bastion.sql
new file mode 100644
index 00000000..a8e290c4
--- /dev/null
+++ b/src/app/db/migrations/0173_neat_bastion.sql
@@ -0,0 +1,26 @@
+CREATE TABLE "deal_measure_approval_events" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "hubspot_deal_id" text NOT NULL,
+ "measure_name" text NOT NULL,
+ "action" text NOT NULL,
+ "acted_by" bigint NOT NULL,
+ "acted_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "deal_measure_approvals" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "hubspot_deal_id" text NOT NULL,
+ "measure_name" text NOT NULL,
+ "is_approved" boolean DEFAULT true NOT NULL,
+ "approved_by" bigint NOT NULL,
+ "approved_at" timestamp with time zone DEFAULT now() NOT NULL,
+ CONSTRAINT "uq_deal_measure" UNIQUE("hubspot_deal_id","measure_name")
+);
+--> statement-breakpoint
+DROP TABLE "deal_approvals" CASCADE;--> statement-breakpoint
+DROP TABLE "deal_approved_measures" CASCADE;--> statement-breakpoint
+ALTER TABLE "deal_measure_approval_events" ADD CONSTRAINT "deal_measure_approval_events_acted_by_user_id_fk" FOREIGN KEY ("acted_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "deal_measure_approvals" ADD CONSTRAINT "deal_measure_approvals_approved_by_user_id_fk" FOREIGN KEY ("approved_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+CREATE INDEX "idx_deal_measure_events_deal_id" ON "deal_measure_approval_events" USING btree ("hubspot_deal_id");--> statement-breakpoint
+CREATE INDEX "idx_deal_measure_events_acted_at" ON "deal_measure_approval_events" USING btree ("acted_at");--> statement-breakpoint
+CREATE INDEX "idx_deal_measure_approvals_deal_id" ON "deal_measure_approvals" USING btree ("hubspot_deal_id");
\ No newline at end of file
diff --git a/src/app/db/migrations/meta/0173_snapshot.json b/src/app/db/migrations/meta/0173_snapshot.json
new file mode 100644
index 00000000..dc892026
--- /dev/null
+++ b/src/app/db/migrations/meta/0173_snapshot.json
@@ -0,0 +1,6754 @@
+{
+ "id": "f8b15e86-b217-4eda-890f-ae4704872d8f",
+ "prevId": "2cd9cc35-10bd-4523-b97a-e1db7eede798",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.postcode_search": {
+ "name": "postcode_search",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "result_data": {
+ "name": "result_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_updated_at": {
+ "name": "last_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "postcode_search_postcode_unique": {
+ "name": "postcode_search_postcode_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "postcode"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deal_measure_approval_events": {
+ "name": "deal_measure_approval_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_name": {
+ "name": "measure_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "acted_by": {
+ "name": "acted_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "acted_at": {
+ "name": "acted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_deal_measure_events_deal_id": {
+ "name": "idx_deal_measure_events_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_deal_measure_events_acted_at": {
+ "name": "idx_deal_measure_events_acted_at",
+ "columns": [
+ {
+ "expression": "acted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deal_measure_approval_events_acted_by_user_id_fk": {
+ "name": "deal_measure_approval_events_acted_by_user_id_fk",
+ "tableFrom": "deal_measure_approval_events",
+ "tableTo": "user",
+ "columnsFrom": [
+ "acted_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.deal_measure_approvals": {
+ "name": "deal_measure_approvals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_name": {
+ "name": "measure_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_approved": {
+ "name": "is_approved",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "approved_by": {
+ "name": "approved_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_deal_measure_approvals_deal_id": {
+ "name": "idx_deal_measure_approvals_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "deal_measure_approvals_approved_by_user_id_fk": {
+ "name": "deal_measure_approvals_approved_by_user_id_fk",
+ "tableFrom": "deal_measure_approvals",
+ "tableTo": "user",
+ "columnsFrom": [
+ "approved_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "uq_deal_measure": {
+ "name": "uq_deal_measure",
+ "nullsNotDistinct": false,
+ "columns": [
+ "hubspot_deal_id",
+ "measure_name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.bulk_address_uploads": {
+ "name": "bulk_address_uploads",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_bucket": {
+ "name": "s3_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_key": {
+ "name": "s3_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filename": {
+ "name": "filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready_for_processing'"
+ },
+ "source_headers": {
+ "name": "source_headers",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "column_mapping": {
+ "name": "column_mapping",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.aspect_condition": {
+ "name": "aspect_condition",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "element_id": {
+ "name": "element_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_type": {
+ "name": "aspect_type",
+ "type": "aspect_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "aspect_instance": {
+ "name": "aspect_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quantity": {
+ "name": "quantity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "install_date": {
+ "name": "install_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "renewal_year": {
+ "name": "renewal_year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "comments": {
+ "name": "comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "aspect_condition_element_id_element_id_fk": {
+ "name": "aspect_condition_element_id_element_id_fk",
+ "tableFrom": "aspect_condition",
+ "tableTo": "element",
+ "columnsFrom": [
+ "element_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.element": {
+ "name": "element",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "survey_id": {
+ "name": "survey_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_type": {
+ "name": "element_type",
+ "type": "element_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_instance": {
+ "name": "element_instance",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "element_survey_id_property_condition_survey_id_fk": {
+ "name": "element_survey_id_property_condition_survey_id_fk",
+ "tableFrom": "element",
+ "tableTo": "property_condition_survey",
+ "columnsFrom": [
+ "survey_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_condition_survey": {
+ "name": "property_condition_survey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_company_data": {
+ "name": "hubspot_company_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "company_name": {
+ "name": "company_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "group_id": {
+ "name": "group_id",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_deal_data": {
+ "name": "hubspot_deal_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deal_id": {
+ "name": "deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dealname": {
+ "name": "dealname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dealstage": {
+ "name": "dealstage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "company_id": {
+ "name": "company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "project_code": {
+ "name": "project_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "listing_id": {
+ "name": "listing_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome_notes": {
+ "name": "outcome_notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_description": {
+ "name": "major_condition_issue_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_photos": {
+ "name": "major_condition_issue_photos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "major_condition_issue_evidence_s3_url": {
+ "name": "major_condition_issue_evidence_s3_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_status": {
+ "name": "coordination_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_status": {
+ "name": "design_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pashub_link": {
+ "name": "pashub_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sharepoint_link": {
+ "name": "sharepoint_link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dampmould_growth": {
+ "name": "dampmould_growth",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pre_sap": {
+ "name": "pre_sap",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordinator": {
+ "name": "coordinator",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_completion_date": {
+ "name": "mtp_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mtp_re_model_completion_date": {
+ "name": "mtp_re_model_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ioe_v3_completion_date": {
+ "name": "ioe_v3_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proposed_measures": {
+ "name": "proposed_measures",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approved_package": {
+ "name": "approved_package",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "designer": {
+ "name": "designer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_type": {
+ "name": "design_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "design_completion_date": {
+ "name": "design_completion_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actual_measures_installed": {
+ "name": "actual_measures_installed",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer": {
+ "name": "installer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installer_handover": {
+ "name": "installer_handover",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_status": {
+ "name": "lodgement_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measures_lodgement_date": {
+ "name": "measures_lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expected_commencement_date": {
+ "name": "expected_commencement_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "coordination_comments": {
+ "name": "coordination_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyor": {
+ "name": "surveyor",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "damp_mould_and_repairs_comments": {
+ "name": "damp_mould_and_repairs_comments",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_date": {
+ "name": "confirmed_survey_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_survey_time": {
+ "name": "confirmed_survey_time",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "surveyed_date": {
+ "name": "surveyed_date",
+ "type": "timestamp (6) with time zone",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.property_status_tracker": {
+ "name": "property_status_tracker",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "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 (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": {
+ "property_status_tracker_property_id_property_id_fk": {
+ "name": "property_status_tracker_property_id_property_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_status_tracker_portfolio_id_portfolio_id_fk": {
+ "name": "property_status_tracker_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_status_tracker",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_store": {
+ "name": "epc_store",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api_created_at": {
+ "name": "epc_api_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_api": {
+ "name": "epc_api",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_created_at": {
+ "name": "epc_page_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page": {
+ "name": "epc_page",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_page_rrn": {
+ "name": "epc_page_rrn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_epc_store_uprn": {
+ "name": "uq_epc_store_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.files_from_surveyor": {
+ "name": "files_from_surveyor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_json_url": {
+ "name": "s3_json_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "files_from_surveyor_portfolio_id_portfolio_id_fk": {
+ "name": "files_from_surveyor_portfolio_id_portfolio_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "files_from_surveyor_property_id_property_id_fk": {
+ "name": "files_from_surveyor_property_id_property_id_fk",
+ "tableFrom": "files_from_surveyor",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package": {
+ "name": "funding_package",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheme": {
+ "name": "scheme",
+ "type": "scheme",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "project_funding": {
+ "name": "project_funding",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_uplift": {
+ "name": "total_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "full_project_score": {
+ "name": "full_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_plan_id_plan_id_fk": {
+ "name": "funding_package_plan_id_plan_id_fk",
+ "tableFrom": "funding_package",
+ "tableTo": "plan",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.funding_package_measures": {
+ "name": "funding_package_measures",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "funding_package_id": {
+ "name": "funding_package_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure": {
+ "name": "measure",
+ "type": "type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "material_id": {
+ "name": "material_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "innovation_uplift": {
+ "name": "innovation_uplift",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "partial_project_score": {
+ "name": "partial_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uplift_project_score": {
+ "name": "uplift_project_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "funding_package_measures_funding_package_id_funding_package_id_fk": {
+ "name": "funding_package_measures_funding_package_id_funding_package_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "funding_package",
+ "columnsFrom": [
+ "funding_package_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "funding_package_measures_material_id_material_id_fk": {
+ "name": "funding_package_measures_material_id_material_id_fk",
+ "tableFrom": "funding_package_measures",
+ "tableTo": "material",
+ "columnsFrom": [
+ "material_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.inspections": {
+ "name": "inspections",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "archetype": {
+ "name": "archetype",
+ "type": "inspection_archetype",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archetype_2": {
+ "name": "archetype_2",
+ "type": "inspection_archetype_2",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_construction": {
+ "name": "wall_construction",
+ "type": "inspections_wall_construction",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation": {
+ "name": "insulation",
+ "type": "inspections_wall_insulation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulation_material": {
+ "name": "insulation_material",
+ "type": "inspections_insulation_material",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "borescoped": {
+ "name": "borescoped",
+ "type": "inspection_borescoped",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_orientation": {
+ "name": "roof_orientation",
+ "type": "inspections_roof_orientation",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tile_hung": {
+ "name": "tile_hung",
+ "type": "inspections_tile_hung",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rendered": {
+ "name": "rendered",
+ "type": "inspections_rendered",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cladding": {
+ "name": "cladding",
+ "type": "inspections_cladding",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_issues": {
+ "name": "access_issues",
+ "type": "inspections_access_issues",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "surveyor_name": {
+ "name": "surveyor_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "inspections_property_id_property_id_fk": {
+ "name": "inspections_property_id_property_id_fk",
+ "tableFrom": "inspections",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.material": {
+ "name": "material",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "type",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_unit": {
+ "name": "cost_unit",
+ "type": "cost_unit",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "innovation_rate": {
+ "name": "innovation_rate",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "size": {
+ "name": "size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "size_unit": {
+ "name": "size_unit",
+ "type": "size_unit",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "includes_scaffolding": {
+ "name": "includes_scaffolding",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "includes_battery": {
+ "name": "includes_battery",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "battery_size": {
+ "name": "battery_size",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organisation": {
+ "name": "organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "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()"
+ },
+ "hubspot_company_id": {
+ "name": "hubspot_company_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.portfolio_organisation": {
+ "name": "portfolio_organisation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organisation_id": {
+ "name": "organisation_id",
+ "type": "uuid",
+ "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": {
+ "portfolio_organisation_portfolio_id_portfolio_id_fk": {
+ "name": "portfolio_organisation_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "portfolio_organisation_organisation_id_organisation_id_fk": {
+ "name": "portfolio_organisation_organisation_id_organisation_id_fk",
+ "tableFrom": "portfolio_organisation",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "organisation_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_organisation_portfolio_id_unique": {
+ "name": "portfolio_organisation_portfolio_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.portfolio_capabilities": {
+ "name": "portfolio_capabilities",
+ "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
+ },
+ "capability": {
+ "name": "capability",
+ "type": "portfolio_capability",
+ "typeSchema": "public",
+ "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": {
+ "portfolio_capabilities_user_id_user_id_fk": {
+ "name": "portfolio_capabilities_user_id_user_id_fk",
+ "tableFrom": "portfolio_capabilities",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "portfolio_capabilities_portfolio_id_portfolio_id_fk": {
+ "name": "portfolio_capabilities_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolio_capabilities",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_capabilities_user_id_portfolio_id_capability_unique": {
+ "name": "portfolio_capabilities_user_id_portfolio_id_capability_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "user_id",
+ "portfolio_id",
+ "capability"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "building_reference_number": {
+ "name": "building_reference_number",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "status",
+ "typeSchema": "public",
+ "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",
+ "typeSchema": "public",
+ "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
+ },
+ "installed_measures_sap_point_adjustment": {
+ "name": "installed_measures_sap_point_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_sap_points_adjusted_for_installed_measures": {
+ "name": "is_sap_points_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "original_sap_points": {
+ "name": "original_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_sap_points": {
+ "name": "lodged_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_epc_rating": {
+ "name": "lodged_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_portfolio_uprn": {
+ "name": "uq_property_portfolio_uprn",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"property\".\"uprn\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "lodgement_date": {
+ "name": "lodgement_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_expired": {
+ "name": "is_expired",
+ "type": "boolean",
+ "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
+ },
+ "sap_05_overwritten": {
+ "name": "sap_05_overwritten",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "sap_05_score": {
+ "name": "sap_05_score",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_05_epc_rating": {
+ "name": "sap_05_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": 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
+ },
+ "original_co2_emissions": {
+ "name": "original_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_primary_energy_consumption": {
+ "name": "original_primary_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand": {
+ "name": "original_current_energy_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_current_energy_demand_heating_hotwater": {
+ "name": "original_current_energy_demand_heating_hotwater",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_co2_adjustment": {
+ "name": "installed_measures_co2_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_energy_demand_adjustment": {
+ "name": "installed_measures_energy_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_total_energy_bill_adjustment": {
+ "name": "installed_measures_total_energy_bill_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "installed_measures_heat_demand_adjustment": {
+ "name": "installed_measures_heat_demand_adjustment",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_epc_adjusted_for_installed_measures": {
+ "name": "is_epc_adjusted_for_installed_measures",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "lodged_co2_emissions": {
+ "name": "lodged_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_heat_demand": {
+ "name": "lodged_heat_demand",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_been_remodelled": {
+ "name": "has_been_remodelled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "environment_impact_current": {
+ "name": "environment_impact_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_property_details_epc_property_portfolio": {
+ "name": "uq_property_details_epc_property_portfolio",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "uq_property_details_spatial_uprn": {
+ "name": "uq_property_details_spatial_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.installed_measure": {
+ "name": "installed_measure",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_type": {
+ "name": "measure_type",
+ "type": "measure_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "installed_at": {
+ "name": "installed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "sap_points": {
+ "name": "sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "carbon_savings": {
+ "name": "carbon_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kwh_savings": {
+ "name": "kwh_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bill_savings": {
+ "name": "bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heat_demand_savings": {
+ "name": "heat_demand_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "idx_installed_measure_uprn": {
+ "name": "idx_installed_measure_uprn",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_active": {
+ "name": "idx_installed_measure_uprn_active",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_measure_type": {
+ "name": "idx_installed_measure_measure_type",
+ "columns": [
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_installed_measure_uprn_measure": {
+ "name": "idx_installed_measure_uprn_measure",
+ "columns": [
+ {
+ "expression": "uprn",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "measure_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"installed_measure\".\"is_active\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "post_sap_points": {
+ "name": "post_sap_points",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_epc_rating": {
+ "name": "post_epc_rating",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_co2_emissions": {
+ "name": "post_co2_emissions",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_savings": {
+ "name": "co2_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_bill": {
+ "name": "post_energy_bill",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_bill_savings": {
+ "name": "energy_bill_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_energy_consumption": {
+ "name": "post_energy_consumption",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_consumption_savings": {
+ "name": "energy_consumption_savings",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_post_retrofit": {
+ "name": "valuation_post_retrofit",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "valuation_increase": {
+ "name": "valuation_increase",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_of_works": {
+ "name": "cost_of_works",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contingency_cost": {
+ "name": "contingency_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plan_type": {
+ "name": "plan_type",
+ "type": "plan_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_plan_portfolio_scenario": {
+ "name": "idx_plan_portfolio_scenario",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_latest_per_property": {
+ "name": "idx_plan_latest_per_property",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "scenario_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {
+ "idx_plan_recommendations_plan_id": {
+ "name": "idx_plan_recommendations_plan_id",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_plan_recommendations_plan_rec": {
+ "name": "idx_plan_recommendations_plan_rec",
+ "columns": [
+ {
+ "expression": "plan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "contingency_cost": {
+ "name": "contingency_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": {
+ "recommendation_property_id_idx": {
+ "name": "recommendation_property_id_idx",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_defaults": {
+ "name": "idx_recommendation_active_defaults",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_recommendation_active_id_property": {
+ "name": "idx_recommendation_active_id_property",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"recommendation\".\"default\" = true AND \"recommendation\".\"already_installed\" = false",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": false
+ },
+ "quantity_unit": {
+ "name": "quantity_unit",
+ "type": "unit_quantity",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "estimated_cost": {
+ "name": "estimated_cost",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "recommendation_materials_recommendation_id_idx": {
+ "name": "recommendation_materials_recommendation_id_idx",
+ "columns": [
+ {
+ "expression": "recommendation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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": "cascade",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal": {
+ "name": "goal",
+ "type": "goal",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "goal_value": {
+ "name": "goal_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ashp_cop": {
+ "name": "ashp_cop",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 2.8
+ },
+ "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
+ },
+ "contingency": {
+ "name": "contingency",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funding": {
+ "name": "funding",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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",
+ "typeSchema": "public",
+ "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": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sub_task": {
+ "name": "sub_task",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cloud_logs_url": {
+ "name": "cloud_logs_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sub_task_task_id_tasks_id_fk": {
+ "name": "sub_task_task_id_tasks_id_fk",
+ "tableFrom": "sub_task",
+ "tableTo": "tasks",
+ "columnsFrom": [
+ "task_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_source": {
+ "name": "task_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "job_started": {
+ "name": "job_started",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "job_completed": {
+ "name": "job_completed",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'In Progress'"
+ },
+ "service": {
+ "name": "service",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team": {
+ "name": "team",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "uuid",
+ "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": {
+ "team_org_id_organisation_id_fk": {
+ "name": "team_org_id_organisation_id_fk",
+ "tableFrom": "team",
+ "tableTo": "organisation",
+ "columnsFrom": [
+ "org_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_members": {
+ "name": "team_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "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": {
+ "team_members_user_id_user_id_fk": {
+ "name": "team_members_user_id_user_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_members_team_id_team_id_fk": {
+ "name": "team_members_team_id_team_id_fk",
+ "tableFrom": "team_members",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.team_portfolio_permissions": {
+ "name": "team_portfolio_permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "team_id": {
+ "name": "team_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "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": {
+ "team_portfolio_permissions_team_id_team_id_fk": {
+ "name": "team_portfolio_permissions_team_id_team_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "team",
+ "columnsFrom": [
+ "team_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "team_portfolio_permissions_portfolio_id_portfolio_id_fk": {
+ "name": "team_portfolio_permissions_portfolio_id_portfolio_id_fk",
+ "tableFrom": "team_portfolio_permissions",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.uploaded_files": {
+ "name": "uploaded_files",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "s3_file_bucket": {
+ "name": "s3_file_bucket",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_file_key": {
+ "name": "s3_file_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "s3_upload_timestamp": {
+ "name": "s3_upload_timestamp",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "landlord_property_id": {
+ "name": "landlord_property_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hubspot_listing_id": {
+ "name": "hubspot_listing_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_type": {
+ "name": "file_type",
+ "type": "file_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_source": {
+ "name": "file_source",
+ "type": "file_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measure_name": {
+ "name": "measure_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "uploaded_files_uploaded_by_user_id_fk": {
+ "name": "uploaded_files_uploaded_by_user_id_fk",
+ "tableFrom": "uploaded_files",
+ "tableTo": "user",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "providerAccountId": {
+ "name": "providerAccountId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_state": {
+ "name": "session_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "account_userId_user_id_fk": {
+ "name": "account_userId_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "account_provider_providerAccountId_pk": {
+ "name": "account_provider_providerAccountId_pk",
+ "columns": [
+ "provider",
+ "providerAccountId"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "sessionToken": {
+ "name": "sessionToken",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "session_userId_user_id_fk": {
+ "name": "session_userId_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": [
+ "userId"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.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
+ },
+ "emailVerified": {
+ "name": "emailVerified",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_id": {
+ "name": "oauth_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_provider": {
+ "name": "oauth_provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "onboarded": {
+ "name": "onboarded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "last_login": {
+ "name": "last_login",
+ "type": "timestamp",
+ "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": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_profiles": {
+ "name": "user_profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_type": {
+ "name": "user_type",
+ "type": "user_profiles_user_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_count": {
+ "name": "property_count",
+ "type": "user_profiles_property_count",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "goals": {
+ "name": "goals",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "referral_source": {
+ "name": "referral_source",
+ "type": "user_profiles_referral_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nrla_membership_id": {
+ "name": "nrla_membership_id",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "accepted_privacy": {
+ "name": "accepted_privacy",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "accepted_privacy_at": {
+ "name": "accepted_privacy_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "marketing_opt_in": {
+ "name": "marketing_opt_in",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "marketing_opt_in_at": {
+ "name": "marketing_opt_in_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_name": {
+ "name": "last_name",
+ "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": {
+ "user_profiles_user_id_user_id_fk": {
+ "name": "user_profiles_user_id_user_id_fk",
+ "tableFrom": "user_profiles",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verificationToken": {
+ "name": "verificationToken",
+ "schema": "",
+ "columns": {
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires": {
+ "name": "expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "verificationToken_identifier_token_pk": {
+ "name": "verificationToken_identifier_token_pk",
+ "columns": [
+ "identifier",
+ "token"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.whlg": {
+ "name": "whlg",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.aspect_type": {
+ "name": "aspect_type",
+ "schema": "public",
+ "values": [
+ "material",
+ "condition",
+ "type",
+ "area",
+ "configuration",
+ "presence",
+ "risk",
+ "severity",
+ "location",
+ "finish",
+ "insulation",
+ "pointing",
+ "spalling",
+ "lintels",
+ "cladding",
+ "category",
+ "quantity",
+ "adequacy",
+ "rating",
+ "strategy",
+ "extent",
+ "distribution",
+ "structure",
+ "covering",
+ "fire_rating",
+ "external_decoration",
+ "work_required",
+ "age_band",
+ "construction_type",
+ "classification",
+ "system"
+ ]
+ },
+ "public.element_type": {
+ "name": "element_type",
+ "schema": "public",
+ "values": [
+ "property",
+ "property_construction_type",
+ "property_classification",
+ "property_age_band",
+ "storey_count",
+ "floor_level",
+ "floor_level_front_door",
+ "accessible_housing_register",
+ "asbestos",
+ "quality_standard",
+ "ccu",
+ "passenger_lift",
+ "stairlift",
+ "disabled_hoist_tracking",
+ "disabled_facilities",
+ "steps_to_front_door",
+ "roof",
+ "pitched_roof_covering",
+ "flat_roof_covering",
+ "rainwater_goods",
+ "loft_insulation",
+ "porch_canopy",
+ "chimney",
+ "fascia",
+ "soffit",
+ "fascia_soffit_bargeboards",
+ "gutters",
+ "store_roof",
+ "garage_roof",
+ "garage_and_store_roof",
+ "external_wall",
+ "external_noise_insulation",
+ "primary_wall",
+ "secondary_wall",
+ "downpipes",
+ "external_decoration",
+ "cladding",
+ "spandrel_panels",
+ "garage_walls",
+ "party_wall_fire_break",
+ "external_brickwork_pointing",
+ "internal_downpipes_external_area",
+ "external_windows",
+ "communal_windows",
+ "secondary_glazing",
+ "store_windows",
+ "garage_windows",
+ "garage_and_store_windows",
+ "external_door",
+ "front_door",
+ "rear_door",
+ "store_door",
+ "garage_door",
+ "garage_and_store_door",
+ "communal_entrance_door",
+ "main_door",
+ "block_entrance_door",
+ "lintel",
+ "patio_french_door",
+ "door_entry_handset",
+ "paths_and_hardstandings",
+ "parking_areas",
+ "boundary_walls",
+ "front_fencing",
+ "rear_fencing",
+ "side_fencing",
+ "rear_gate",
+ "front_gate",
+ "gates",
+ "retaining_walls",
+ "private_balcony",
+ "balcony_balustrade",
+ "outbuildings",
+ "garage_structure",
+ "paving",
+ "roads",
+ "soil_and_vent",
+ "solar_thermals",
+ "drop_kerb",
+ "outbuilding_overhaul",
+ "external_structural_defects",
+ "access_ramp",
+ "kitchen",
+ "kitchen_space_layout",
+ "tenant_installed_kitchen",
+ "kitchen_extractor_fan",
+ "bathroom",
+ "secondary_bathroom",
+ "secondary_toilet",
+ "bathroom_extractor_fan",
+ "additional_wc_or_whb",
+ "bathroom_remaining_life_source",
+ "kitchen_remaining_life_source",
+ "central_heating",
+ "heating_boiler",
+ "heating_distribution",
+ "secondary_heating",
+ "hot_water_system",
+ "cold_water_storage",
+ "heating_system",
+ "boiler_fuel",
+ "water_heating",
+ "programmable_heating",
+ "community_heating",
+ "gas_available",
+ "heat_recovery_units",
+ "heating_improvements",
+ "electrical_wiring",
+ "consumer_unit",
+ "smoke_detection",
+ "heat_detection",
+ "carbon_monoxide_detection",
+ "fire_door_rating",
+ "fire_risk_assessment",
+ "internal_wiring",
+ "electrics",
+ "communal_heating",
+ "communal_boiler",
+ "communal_electrics",
+ "communal_fire_alarm",
+ "communal_emergency_lighting",
+ "communal_door_entry",
+ "communal_cctv",
+ "communal_bin_store",
+ "communal_bin_store_doors",
+ "communal_bin_store_walls",
+ "communal_bin_store_roof",
+ "communal_refuse_chute",
+ "communal_floor_covering",
+ "communal_kitchen",
+ "communal_bathroom",
+ "communal_toilets",
+ "communal_gates",
+ "communal_lift",
+ "communal_passenger_lift",
+ "communal_balcony_walkway",
+ "communal_entrance",
+ "communal_internal_decorations",
+ "communal_internal_floor",
+ "communal_walkways",
+ "communal_external_doors",
+ "communal_stairs",
+ "communal_aerial",
+ "communal_aov",
+ "communal_internal_doors",
+ "communal_lateral_mains",
+ "communal_lighting",
+ "communal_lighting_conductor",
+ "communal_store_roof",
+ "communal_store_walls",
+ "communal_store_doors",
+ "communal_warden_call_system",
+ "communal_bms",
+ "communal_booster_pump",
+ "communal_dry_riser",
+ "communal_wet_riser",
+ "communal_cold_water_storage",
+ "communal_sprinkler",
+ "communal_plug_sockets",
+ "communal_circulation_space",
+ "ffhh_damp",
+ "ffhh_hold_and_cold_water",
+ "ffhh_drainage_lavatories",
+ "ffhh_neglected",
+ "ffhh_natural_light",
+ "ffhh_ventilation",
+ "ffhh_food_prep_and_washup",
+ "ffhh_unsafe_layout",
+ "ffhh_unstable_building",
+ "hhsrs_damp_and_mould",
+ "hhsrs_excess_cold",
+ "hhsrs_excess_heat",
+ "hhsrs_asbestos_and_mmf",
+ "hhsrs_biocides",
+ "hhsrs_carbon_monoxide",
+ "hhsrs_lead",
+ "hhsrs_radiation",
+ "hhsrs_uncombusted_fuel_gas",
+ "hhsrs_volatile_organic_compounds",
+ "hhsrs_crowding_and_space",
+ "hhsrs_entry_by_intruders",
+ "hhsrs_lighting",
+ "hhsrs_noise",
+ "hhsrs_domestic_hygiene_pests_refuse",
+ "hhsrs_food_safety",
+ "hhsrs_personal_hygiene_sanitation",
+ "hhsrs_water_supply",
+ "hhsrs_falls_associated_with_baths",
+ "hhsrs_falls_on_level_surfaces",
+ "hhsrs_falls_on_stairs",
+ "hhsrs_falls_between_levels",
+ "hhsrs_electrical_hazards",
+ "hhsrs_fire",
+ "hhsrs_flames_hot_surfaces",
+ "hhsrs_collision_and_entrapment",
+ "hhsrs_collision_hazards_low_headroom",
+ "hhsrs_explosions",
+ "hhsrs_ergonomics",
+ "hhsrs_structural_collapse",
+ "hhsrs_amenities"
+ ]
+ },
+ "public.document_type": {
+ "name": "document_type",
+ "schema": "public",
+ "values": [
+ "EPR",
+ "Condition Report",
+ "Evidence Report",
+ "Summary Information",
+ "Floor Plan",
+ "Scenario Draft EPC",
+ "Scenario Site Notes"
+ ]
+ },
+ "public.scheme": {
+ "name": "scheme",
+ "schema": "public",
+ "values": [
+ "eco4",
+ "gbis",
+ "whlg",
+ "none"
+ ]
+ },
+ "public.inspection_archetype_2": {
+ "name": "inspection_archetype_2",
+ "schema": "public",
+ "values": [
+ "detached",
+ "mid-terrace",
+ "enclosed mid-terrace",
+ "end-terrace",
+ "enclosed end-terrace",
+ "semi-detached"
+ ]
+ },
+ "public.inspection_archetype": {
+ "name": "inspection_archetype",
+ "schema": "public",
+ "values": [
+ "Bungalow",
+ "Flat",
+ "Maisonette",
+ "House",
+ "non-domestic"
+ ]
+ },
+ "public.inspection_borescoped": {
+ "name": "inspection_borescoped",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "refused"
+ ]
+ },
+ "public.inspections_access_issues": {
+ "name": "inspections_access_issues",
+ "schema": "public",
+ "values": [
+ "see notes",
+ "damp issues",
+ "foliage on walls",
+ "bushes against wall",
+ "trees around/anove property",
+ "high rise block flats/maisonettes",
+ "conservatory",
+ "lean-to",
+ "garage",
+ "extension",
+ "decking",
+ "shed against wall"
+ ]
+ },
+ "public.inspections_cladding": {
+ "name": "inspections_cladding",
+ "schema": "public",
+ "values": [
+ "none",
+ "cladded with “sufficient space to fill the wall”",
+ "cladded with “insufficient space to fill the wall”"
+ ]
+ },
+ "public.inspections_insulation_material": {
+ "name": "inspections_insulation_material",
+ "schema": "public",
+ "values": [
+ "empty 50-90",
+ "empty 100+",
+ "empty 30-40",
+ "empty less than 30",
+ "loose fibre/wool",
+ "eps/celo/king",
+ "fibre batts - with cavity",
+ "fibre batts - no cavity",
+ "loose bead",
+ "glued bead",
+ "formaldehyde",
+ "bubble wrap",
+ "poly chunks"
+ ]
+ },
+ "public.inspections_rendered": {
+ "name": "inspections_rendered",
+ "schema": "public",
+ "values": [
+ "no render",
+ "rendered with “insufficient” space between dpc and render",
+ "rendered with “sufficient” space between dpc and render"
+ ]
+ },
+ "public.inspections_roof_orientation": {
+ "name": "inspections_roof_orientation",
+ "schema": "public",
+ "values": [
+ "north",
+ "east",
+ "south",
+ "west",
+ "north-east",
+ "north-west",
+ "south-east",
+ "south-west",
+ "n/s split",
+ "e/w split",
+ "ne/sw split",
+ "nw/se split",
+ "flat roof",
+ "no roof",
+ "roof too small",
+ "already has solar pv"
+ ]
+ },
+ "public.inspections_tile_hung": {
+ "name": "inspections_tile_hung",
+ "schema": "public",
+ "values": [
+ "yes",
+ "no",
+ "first floor flats are tile hung"
+ ]
+ },
+ "public.inspections_wall_construction": {
+ "name": "inspections_wall_construction",
+ "schema": "public",
+ "values": [
+ "cavity",
+ "solid",
+ "system built",
+ "timber framed",
+ "steel framed",
+ "re-walled cavity",
+ "mansard pre-fab",
+ "mansard ewi",
+ "mansard re-walled"
+ ]
+ },
+ "public.inspections_wall_insulation": {
+ "name": "inspections_wall_insulation",
+ "schema": "public",
+ "values": [
+ "empty cavity",
+ "filled at build",
+ "partial",
+ "retro drilled",
+ "ewi",
+ "iwi",
+ "solid non-cavity",
+ "system built",
+ "timber framed",
+ "steel framed"
+ ]
+ },
+ "public.cost_unit": {
+ "name": "cost_unit",
+ "schema": "public",
+ "values": [
+ "gbp_sq_meter",
+ "gbp_per_unit",
+ "gbp_per_m2",
+ "gbp_per_m"
+ ]
+ },
+ "public.depth_unit": {
+ "name": "depth_unit",
+ "schema": "public",
+ "values": [
+ "mm"
+ ]
+ },
+ "public.type": {
+ "name": "type",
+ "schema": "public",
+ "values": [
+ "suspended_floor_insulation",
+ "solid_floor_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "cavity_wall_insulation",
+ "mechanical_ventilation",
+ "loft_insulation",
+ "exposed_floor_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "cavity_wall_extraction",
+ "iwi_wall_demolition",
+ "iwi_vapour_barrier",
+ "iwi_redecoration",
+ "suspended_floor_demolition",
+ "suspended_floor_redecoration",
+ "suspended_floor_vapour_barrier",
+ "solid_floor_demolition",
+ "solid_floor_preparation",
+ "solid_floor_vapour_barrier",
+ "solid_floor_redecoration",
+ "ewi_wall_demolition",
+ "ewi_wall_preparation",
+ "ewi_wall_redecoration",
+ "low_energy_lighting_installation",
+ "flat_roof_preparation",
+ "flat_roof_vapour_barrier",
+ "flat_roof_waterproofing",
+ "windows_glazing",
+ "secondary_glazing",
+ "double_glazing",
+ "trickle_vent",
+ "door_undercut",
+ "solar_pv",
+ "solar_battery",
+ "scaffolding",
+ "high_heat_retention_storage_heaters",
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "sealing_fireplace"
+ ]
+ },
+ "public.r_value_unit": {
+ "name": "r_value_unit",
+ "schema": "public",
+ "values": [
+ "square_meter_kelvin_per_watt"
+ ]
+ },
+ "public.size_unit": {
+ "name": "size_unit",
+ "schema": "public",
+ "values": [
+ "kWp",
+ "kW",
+ "watt",
+ "storey"
+ ]
+ },
+ "public.thermal_conductivity_unit": {
+ "name": "thermal_conductivity_unit",
+ "schema": "public",
+ "values": [
+ "watt_per_meter_kelvin"
+ ]
+ },
+ "public.goal": {
+ "name": "goal",
+ "schema": "public",
+ "values": [
+ "Valuation Improvement",
+ "Increasing EPC",
+ "Reducing CO2 emissions",
+ "Energy Savings",
+ "None"
+ ]
+ },
+ "public.portfolio_capability": {
+ "name": "portfolio_capability",
+ "schema": "public",
+ "values": [
+ "approver",
+ "contractor"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "creator",
+ "admin",
+ "read",
+ "write"
+ ]
+ },
+ "public.status": {
+ "name": "status",
+ "schema": "public",
+ "values": [
+ "scoping",
+ "survey",
+ "assessment",
+ "tendering",
+ "project underway",
+ "completion; status: on track",
+ "completion; status: delayed",
+ "completion; status: at risk",
+ "completion; status: completed",
+ "needs review"
+ ]
+ },
+ "public.epc": {
+ "name": "epc",
+ "schema": "public",
+ "values": [
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G"
+ ]
+ },
+ "public.creation_status": {
+ "name": "creation_status",
+ "schema": "public",
+ "values": [
+ "LOADING",
+ "READY",
+ "ERROR"
+ ]
+ },
+ "public.housing_type": {
+ "name": "housing_type",
+ "schema": "public",
+ "values": [
+ "Private",
+ "Social"
+ ]
+ },
+ "public.measure_type": {
+ "name": "measure_type",
+ "schema": "public",
+ "values": [
+ "air_source_heat_pump",
+ "boiler_upgrade",
+ "high_heat_retention_storage_heaters",
+ "secondary_heating",
+ "roomstat_programmer_trvs",
+ "time_temperature_zone_control",
+ "cylinder_thermostat",
+ "cavity_wall_insulation",
+ "extension_cavity_wall_insulation",
+ "external_wall_insulation",
+ "internal_wall_insulation",
+ "loft_insulation",
+ "flat_roof_insulation",
+ "room_roof_insulation",
+ "solid_floor_insulation",
+ "suspended_floor_insulation",
+ "double_glazing",
+ "secondary_glazing",
+ "draught_proofing",
+ "mechanical_ventilation",
+ "low_energy_lighting",
+ "solar_pv",
+ "hot_water_tank_insulation",
+ "sealing_open_fireplace",
+ "damp_mould",
+ "door_undercut",
+ "extractor_fan",
+ "loft_board",
+ "trickle_vent"
+ ]
+ },
+ "public.plan_type": {
+ "name": "plan_type",
+ "schema": "public",
+ "values": [
+ "solar_eco4",
+ "solar_hhrsh_eco4",
+ "empty_cavity_eco",
+ "partial_cavity_eco",
+ "extraction_eco"
+ ]
+ },
+ "public.unit_quantity": {
+ "name": "unit_quantity",
+ "schema": "public",
+ "values": [
+ "m2",
+ "part",
+ "kwp"
+ ]
+ },
+ "public.scenario_type": {
+ "name": "scenario_type",
+ "schema": "public",
+ "values": [
+ "unit",
+ "building"
+ ]
+ },
+ "public.source": {
+ "name": "source",
+ "schema": "public",
+ "values": [
+ "portfolio_id"
+ ]
+ },
+ "public.file_source": {
+ "name": "file_source",
+ "schema": "public",
+ "values": [
+ "pas hub",
+ "sharepoint",
+ "hubspot",
+ "ecmk",
+ "contractor"
+ ]
+ },
+ "public.file_type": {
+ "name": "file_type",
+ "schema": "public",
+ "values": [
+ "photo_pack",
+ "site_note",
+ "rd_sap_site_note",
+ "pas_2023_ventilation",
+ "pas_2023_condition",
+ "pas_significance",
+ "par_photo_pack",
+ "pas_2023_property",
+ "pas_2023_occupancy",
+ "ecmk_site_note",
+ "ecmk_rd_sap_site_note",
+ "ecmk_survey_xml",
+ "pre_photo",
+ "mid_photo",
+ "post_photo",
+ "pre_installation_building_inspection",
+ "claim_of_compliance",
+ "handover_pack",
+ "insurance_guarantee",
+ "installer_qualifications",
+ "mcs_compliance_certificate",
+ "minor_works_electrical_certificate",
+ "point_of_work_risk_assessment",
+ "installer_feedback",
+ "workmanship_warranty",
+ "g98_notification",
+ "certificate_of_conformity",
+ "ventilation_assessment_checklist",
+ "contractor_other"
+ ]
+ },
+ "public.user_profiles_property_count": {
+ "name": "user_profiles_property_count",
+ "schema": "public",
+ "values": [
+ "1",
+ "2–5",
+ "6–20",
+ "21+",
+ "1–50",
+ "51–100",
+ "101–300",
+ "301–1000",
+ "1000+"
+ ]
+ },
+ "public.user_profiles_referral_source": {
+ "name": "user_profiles_referral_source",
+ "schema": "public",
+ "values": [
+ "search",
+ "social_media",
+ "NRLA",
+ "partner",
+ "word_of_mouth",
+ "other"
+ ]
+ },
+ "public.user_profiles_user_type": {
+ "name": "user_profiles_user_type",
+ "schema": "public",
+ "values": [
+ "private_landlord",
+ "private_tenant",
+ "social_landlord",
+ "social_tenant",
+ "homeowner",
+ "other"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ 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 89e4930a..e0fb9824 100644
--- a/src/app/db/migrations/meta/_journal.json
+++ b/src/app/db/migrations/meta/_journal.json
@@ -1212,6 +1212,13 @@
"when": 1776374085626,
"tag": "0172_kind_eddie_brock",
"breakpoints": true
+ },
+ {
+ "idx": 173,
+ "version": "7",
+ "when": 1776378570612,
+ "tag": "0173_neat_bastion",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/app/db/schema/approvals.ts b/src/app/db/schema/approvals.ts
index 92031500..4d615d53 100644
--- a/src/app/db/schema/approvals.ts
+++ b/src/app/db/schema/approvals.ts
@@ -1,54 +1,65 @@
import {
bigserial,
+ boolean,
text,
timestamp,
pgTable,
bigint,
index,
+ unique,
} from "drizzle-orm/pg-core";
import { user } from "./users";
import { InferModel } from "drizzle-orm";
-export const dealApprovals = pgTable(
- "deal_approvals",
+// Current approval state per (deal, measure) — upserted on each change.
+// Query WHERE is_approved = true to get the currently approved set.
+export const dealMeasureApprovals = pgTable(
+ "deal_measure_approvals",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
hubspotDealId: text("hubspot_deal_id").notNull(),
+ measureName: text("measure_name").notNull(),
+ isApproved: boolean("is_approved").notNull().default(true),
approvedBy: bigint("approved_by", { mode: "bigint" })
.notNull()
.references(() => user.id),
approvedAt: timestamp("approved_at", { withTimezone: true })
.defaultNow()
.notNull(),
- notes: text("notes"),
- createdAt: timestamp("created_at", { withTimezone: true })
- .defaultNow()
- .notNull(),
},
- (table) => [index("idx_deal_approvals_deal_id").on(table.hubspotDealId)],
+ (table) => [
+ unique("uq_deal_measure").on(table.hubspotDealId, table.measureName),
+ index("idx_deal_measure_approvals_deal_id").on(table.hubspotDealId),
+ ],
);
-export const dealApprovedMeasures = pgTable(
- "deal_approved_measures",
+// Append-only audit log — never deleted.
+export const dealMeasureApprovalEvents = pgTable(
+ "deal_measure_approval_events",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
- dealApprovalId: bigint("deal_approval_id", { mode: "bigint" })
- .notNull()
- .references(() => dealApprovals.id, { onDelete: "cascade" }),
- // Stored as text to match free-text proposedMeasures from HubSpot
+ hubspotDealId: text("hubspot_deal_id").notNull(),
measureName: text("measure_name").notNull(),
- createdAt: timestamp("created_at", { withTimezone: true })
+ // 'approved' | 'unapproved'
+ action: text("action").notNull(),
+ actedBy: bigint("acted_by", { mode: "bigint" })
+ .notNull()
+ .references(() => user.id),
+ actedAt: timestamp("acted_at", { withTimezone: true })
.defaultNow()
.notNull(),
},
(table) => [
- index("idx_deal_approved_measures_approval_id").on(table.dealApprovalId),
+ index("idx_deal_measure_events_deal_id").on(table.hubspotDealId),
+ index("idx_deal_measure_events_acted_at").on(table.actedAt),
],
);
-export type DealApproval = InferModel;
-export type NewDealApproval = InferModel;
-export type DealApprovedMeasure = InferModel<
- typeof dealApprovedMeasures,
+export type DealMeasureApproval = InferModel<
+ typeof dealMeasureApprovals,
+ "select"
+>;
+export type DealMeasureApprovalEvent = InferModel<
+ typeof dealMeasureApprovalEvents,
"select"
>;
diff --git a/src/app/portfolio/[slug]/(portfolio)/settings/UsersPermissionsCard.tsx b/src/app/portfolio/[slug]/(portfolio)/settings/UsersPermissionsCard.tsx
index dc2d5cf7..94fc4256 100644
--- a/src/app/portfolio/[slug]/(portfolio)/settings/UsersPermissionsCard.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/settings/UsersPermissionsCard.tsx
@@ -27,15 +27,13 @@ async function getPortfolioUsers(portfolioId: string): Promise {
const users = Array.isArray(json) ? json : json.users; // support both shapes
// Guard + shape to Collaborator[]
return Array.isArray(users)
- ? users
- .filter((u: any) => u.role !== "creator") // 👈 filter out creator
- .map((u: any) => ({
- portfolioUserId: String(u.portfolioUserId),
- userId: String(u.userId),
- name: u.name ?? null,
- email: u.email ?? "",
- role: u.role,
- }))
+ ? users.map((u: any) => ({
+ portfolioUserId: String(u.portfolioUserId),
+ userId: String(u.userId),
+ name: u.name ?? null,
+ email: u.email ?? "",
+ role: u.role,
+ }))
: [];
}
@@ -251,12 +249,20 @@ export function UsersPermissionsCard({ portfolioId }: { portfolioId: string }) {
{c.name || "—"}
{c.email}
- onChangeRole(c.portfolioUserId, r)} />
+ {c.role === "creator" || c.role === "admin" ? (
+
+ {c.role}
+
+ ) : (
+ onChangeRole(c.portfolioUserId, r)} />
+ )}
-
+ {c.role !== "creator" && (
+
+ )}
))
diff --git a/src/app/portfolio/[slug]/(portfolio)/settings/roles.tsx b/src/app/portfolio/[slug]/(portfolio)/settings/roles.tsx
index feeb223e..7fd570b3 100644
--- a/src/app/portfolio/[slug]/(portfolio)/settings/roles.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/settings/roles.tsx
@@ -13,10 +13,10 @@ export type Role = typeof ROLE_OPTIONS[number];
export type Collaborator = {
portfolioUserId: string;
- userId: string;
+ userId: string;
name?: string | null;
email: string;
- role: Role;
+ role: Role | "creator" | "admin";
};
// Small role dropdown using shadcn Select
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ApprovalConfirmDialog.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ApprovalConfirmDialog.tsx
new file mode 100644
index 00000000..ded3a4a8
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ApprovalConfirmDialog.tsx
@@ -0,0 +1,140 @@
+"use client";
+
+import { useState } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/app/shadcn_components/ui/dialog";
+import { Button } from "@/app/shadcn_components/ui/button";
+import { Input } from "@/app/shadcn_components/ui/input";
+import { CheckCircle2, XCircle } from "lucide-react";
+
+export type PendingDiff = {
+ added: string[];
+ removed: string[];
+};
+
+type Props = {
+ open: boolean;
+ pendingDiffs: Record; // dealId -> diff
+ dealNames: Record; // dealId -> display name
+ onConfirm: () => void;
+ onCancel: () => void;
+ isPending: boolean;
+};
+
+const CONFIRM_WORD = "approve";
+
+export function ApprovalConfirmDialog({
+ open,
+ pendingDiffs,
+ dealNames,
+ onConfirm,
+ onCancel,
+ isPending,
+}: Props) {
+ const [typed, setTyped] = useState("");
+
+ const canConfirm = typed === CONFIRM_WORD && !isPending;
+
+ const totalAdded = Object.values(pendingDiffs).reduce(
+ (sum, d) => sum + d.added.length,
+ 0,
+ );
+ const totalRemoved = Object.values(pendingDiffs).reduce(
+ (sum, d) => sum + d.removed.length,
+ 0,
+ );
+
+ function handleOpenChange(open: boolean) {
+ if (!open) {
+ setTyped("");
+ onCancel();
+ }
+ }
+
+ return (
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
new file mode 100644
index 00000000..0d6de222
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
@@ -0,0 +1,569 @@
+"use client";
+
+import { useEffect, useRef, useState } from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/app/shadcn_components/ui/dialog";
+import { Button } from "@/app/shadcn_components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/app/shadcn_components/ui/select";
+import { CheckCircle2, XCircle, Upload, Loader2, Clock } from "lucide-react";
+import { uploadFileToS3 } from "@/app/utils/s3";
+import type { ClassifiedDeal } from "./types";
+
+// ── Types ─────────────────────────────────────────────────────────────────
+
+type FileStatus = "queued" | "uploading" | "done" | "error";
+
+type FileEntry = {
+ id: string; // local UUID for React key
+ // One of these will be set:
+ file?: File; // for newly picked files
+ existingS3Key?: string; // for pre-existing unclassified uploads
+ // Display
+ displayName: string;
+ displaySize?: string;
+ // Upload state
+ status: FileStatus;
+ errorMsg?: string;
+ uploadedId?: string; // DB record ID (set after recording or from existing)
+ // Classification
+ docType: string;
+ measureName: string;
+};
+
+type Phase = "loading" | "upload" | "classify";
+
+type Props = {
+ deal: ClassifiedDeal;
+ portfolioId: string;
+ onClose: () => void;
+};
+
+// ── Constants ─────────────────────────────────────────────────────────────
+
+const FILE_TYPE_OPTIONS: { value: string; label: string; group: string }[] = [
+ { value: "pre_photo", label: "Pre Photo", group: "Install Photos" },
+ { value: "mid_photo", label: "Mid Photo", group: "Install Photos" },
+ { value: "post_photo", label: "Post Photo", group: "Install Photos" },
+ { value: "pre_installation_building_inspection", label: "Pre-Installation Building Inspection (PIBI)", group: "Pre-Installation" },
+ { value: "point_of_work_risk_assessment", label: "Point of Work Risk Assessment", group: "Pre-Installation" },
+ { value: "claim_of_compliance", label: "Claim of Compliance (PAS 2030)", group: "Compliance" },
+ { value: "mcs_compliance_certificate", label: "MCS Compliance Certificate", group: "Compliance" },
+ { value: "certificate_of_conformity", label: "Certificate of Conformity", group: "Compliance" },
+ { value: "minor_works_electrical_certificate", label: "Minor Works Electrical Certificate", group: "Compliance" },
+ { value: "handover_pack", label: "Handover Documents / Pack", group: "Handover" },
+ { value: "workmanship_warranty", label: "Workmanship Warranty", group: "Handover" },
+ { value: "insurance_guarantee", label: "Insurance Backed Guarantee (IBG)", group: "Handover" },
+ { value: "g98_notification", label: "G98 / G99 Notification", group: "Handover" },
+ { value: "ventilation_assessment_checklist", label: "Ventilation Assessment Checklist", group: "Handover" },
+ { value: "installer_qualifications", label: "Installer Qualifications", group: "Qualifications" },
+ { value: "installer_feedback", label: "Installer Feedback", group: "Other" },
+ { value: "contractor_other", label: "Other", group: "Other" },
+];
+
+const FILE_TYPE_GROUPS = ["Install Photos", "Pre-Installation", "Compliance", "Handover", "Qualifications", "Other"];
+
+// ── Helpers ───────────────────────────────────────────────────────────────
+
+function formatSize(bytes: number): string {
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`;
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
+}
+
+function contentTypeFor(ext: string): string {
+ const e = ext.toLowerCase();
+ if (e === "pdf") return "application/pdf";
+ if (["jpg", "jpeg"].includes(e)) return "image/jpeg";
+ if (e === "png") return "image/png";
+ return "application/octet-stream";
+}
+
+function parseMeasures(raw: string | null | undefined): string[] {
+ if (!raw) return [];
+ return raw.split(",").map((m) => m.trim()).filter(Boolean);
+}
+
+function s3KeyBasename(key: string): string {
+ return key.split("/").pop() ?? key;
+}
+
+async function getPresignedUrl(path: string, contentType: string): Promise {
+ const res = await fetch("/api/upload/retrofit-energy-assessments", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ path, contentType, expiresInSeconds: 300 }),
+ });
+ if (!res.ok) throw new Error("Failed to get presigned URL");
+ const { url } = await res.json();
+ return url;
+}
+
+async function recordUpload(payload: {
+ s3FileKey: string;
+ s3FileBucket: string;
+ uprn?: string;
+ hubspotDealId?: string;
+ landlordPropertyId?: string;
+}): Promise {
+ const res = await fetch("/api/upload/contractor-install", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(payload),
+ });
+ if (!res.ok) throw new Error("Failed to record upload");
+ const { id } = await res.json();
+ return id;
+}
+
+async function saveClassifications(
+ updates: { id: string; fileType: string; measureName?: string }[],
+): Promise {
+ const res = await fetch("/api/upload/contractor-install", {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ updates }),
+ });
+ if (!res.ok) throw new Error("Failed to save classifications");
+}
+
+// ── DocType select ─────────────────────────────────────────────────────────
+
+function DocTypeSelect({ value, onChange }: { value: string; onChange: (v: string) => void }) {
+ return (
+
+ );
+}
+
+// ── Status icon ────────────────────────────────────────────────────────────
+
+function StatusIcon({ status, isExisting, errorMsg }: { status: FileStatus; isExisting?: boolean; errorMsg?: string }) {
+ if (isExisting) return ;
+ if (status === "queued") return ;
+ if (status === "uploading") return ;
+ if (status === "done") return ;
+ return ;
+}
+
+// ── Main component ─────────────────────────────────────────────────────────
+
+export default function ContractorUploadModal({ deal, portfolioId, onClose }: Props) {
+ const measures = parseMeasures(deal.proposedMeasures);
+ const fileInputRef = useRef(null);
+ const [isDragOver, setIsDragOver] = useState(false);
+ const [queue, setQueue] = useState([]);
+ const [phase, setPhase] = useState("loading");
+ const [isUploading, setIsUploading] = useState(false);
+ const [isSaving, setIsSaving] = useState(false);
+ const [saveError, setSaveError] = useState(null);
+
+ // ── Fetch existing unclassified files on mount ───────────────────────
+
+ useEffect(() => {
+ async function fetchExisting() {
+ const uprnParam = deal.uprn;
+ const propIdParam = deal.landlordPropertyId;
+ if (!uprnParam && !propIdParam) {
+ setPhase("upload");
+ return;
+ }
+
+ try {
+ const param = uprnParam
+ ? `uprn=${encodeURIComponent(uprnParam)}`
+ : `landlordPropertyId=${encodeURIComponent(propIdParam!)}`;
+ const res = await fetch(`/api/live-tracking/property-documents?${param}`);
+ if (!res.ok) throw new Error("fetch failed");
+ const docs: { id: string; s3FileKey: string; docType: string | null; source: string | null }[] = await res.json();
+
+ const unclassified = docs.filter(
+ (d) => d.source === "contractor" && (d.docType === null || d.docType === "unknown"),
+ );
+
+ if (unclassified.length > 0) {
+ const entries: FileEntry[] = unclassified.map((d) => ({
+ id: crypto.randomUUID(),
+ existingS3Key: d.s3FileKey,
+ displayName: s3KeyBasename(d.s3FileKey),
+ status: "done",
+ uploadedId: d.id,
+ docType: "",
+ measureName: measures[0] ?? "",
+ }));
+ setQueue(entries);
+ setPhase("classify");
+ } else {
+ setPhase("upload");
+ }
+ } catch {
+ // If fetch fails, just proceed to upload phase
+ setPhase("upload");
+ }
+ }
+
+ fetchExisting();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ // ── File selection ───────────────────────────────────────────────────
+
+ function addFiles(files: FileList | File[]) {
+ const newEntries: FileEntry[] = Array.from(files).map((f) => ({
+ id: crypto.randomUUID(),
+ file: f,
+ displayName: f.name,
+ displaySize: formatSize(f.size),
+ status: "queued",
+ docType: "",
+ measureName: measures[0] ?? "",
+ }));
+ setQueue((prev) => [...prev, ...newEntries]);
+ }
+
+ function handleInputChange(e: React.ChangeEvent) {
+ if (e.target.files?.length) addFiles(e.target.files);
+ e.target.value = "";
+ }
+
+ function handleDrop(e: React.DragEvent) {
+ e.preventDefault();
+ setIsDragOver(false);
+ if (e.dataTransfer.files?.length) addFiles(e.dataTransfer.files);
+ }
+
+ function removeFile(id: string) {
+ setQueue((prev) => prev.filter((f) => f.id !== id));
+ }
+
+ // ── Phase 1: Upload new files ────────────────────────────────────────
+
+ async function handleUpload() {
+ const toUpload = queue.filter((f) => f.status === "queued");
+ if (toUpload.length === 0) {
+ // No new files to upload — go straight to classify for existing
+ setPhase("classify");
+ return;
+ }
+ if (isUploading) return;
+ setIsUploading(true);
+
+ setQueue((prev) =>
+ prev.map((f) => f.status === "queued" ? { ...f, status: "uploading" } : f),
+ );
+
+ const uploadResults = await Promise.allSettled(
+ toUpload.map(async (entry) => {
+ const ext = (entry.file!.name.split(".").pop() ?? "bin").toLowerCase();
+ const ct = contentTypeFor(ext);
+ const timestamp = Date.now();
+ const s3Key = `contractor-install/${deal.dealId}/unclassified/${timestamp}_${entry.id.slice(0, 8)}.${ext}`;
+
+ const presignedUrl = await getPresignedUrl(s3Key, ct);
+ await uploadFileToS3({ presignedUrl, file: entry.file!, contentType: ct });
+
+ const urlObj = new URL(presignedUrl);
+ const bucket = urlObj.hostname.split(".")[0];
+
+ const uploadedId = await recordUpload({
+ s3FileKey: s3Key,
+ s3FileBucket: bucket,
+ uprn: deal.uprn ?? undefined,
+ hubspotDealId: deal.dealId,
+ landlordPropertyId: deal.landlordPropertyId ?? undefined,
+ });
+
+ return { id: entry.id, uploadedId };
+ }),
+ );
+
+ const resultMap = new Map(
+ uploadResults.map((r, i) => [
+ toUpload[i].id,
+ r.status === "fulfilled" ? { ok: true, uploadedId: r.value.uploadedId } : { ok: false },
+ ]),
+ );
+
+ setQueue((prev) =>
+ prev.map((f) => {
+ const r = resultMap.get(f.id);
+ if (!r) return f;
+ if (r.ok) return { ...f, status: "done", uploadedId: r.uploadedId };
+ return { ...f, status: "error", errorMsg: "Upload failed" };
+ }),
+ );
+
+ setIsUploading(false);
+ setPhase("classify");
+ }
+
+ // ── Phase 2: Classify ────────────────────────────────────────────────
+
+ function updateEntryField(id: string, field: "docType" | "measureName", value: string) {
+ setQueue((prev) => prev.map((f) => (f.id === id ? { ...f, [field]: value } : f)));
+ }
+
+ const classifiableEntries = queue.filter((f) => f.status === "done" && f.uploadedId);
+ const allClassified = classifiableEntries.length > 0 && classifiableEntries.every((f) => f.docType !== "");
+
+ async function handleSaveClassifications() {
+ setSaveError(null);
+ setIsSaving(true);
+ try {
+ await saveClassifications(
+ classifiableEntries.map((f) => ({
+ id: f.uploadedId!,
+ fileType: f.docType,
+ measureName: f.measureName || undefined,
+ })),
+ );
+ onClose();
+ } catch {
+ setSaveError("Failed to save classifications. Please try again.");
+ } finally {
+ setIsSaving(false);
+ }
+ }
+
+ // ── Computed ─────────────────────────────────────────────────────────
+
+ const newQueuedCount = queue.filter((f) => f.status === "queued").length;
+ const existingCount = queue.filter((f) => f.existingS3Key && f.status === "done").length;
+ const propertyLabel = deal.dealname ?? deal.landlordPropertyId ?? deal.dealId;
+
+ // ── Render ───────────────────────────────────────────────────────────
+
+ return (
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
index b89ba9b1..ac9b657a 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
@@ -28,7 +28,8 @@ import {
} from "@/app/shadcn_components/ui/select";
import { Search, ChevronLeft, ChevronRight, Download } from "lucide-react";
import { createDocumentTableColumns } from "./DocumentTableColumns";
-import type { ClassifiedDeal, DocStatusMap } from "./types";
+import ContractorUploadModal from "./ContractorUploadModal";
+import type { ClassifiedDeal, DocStatusMap, PortfolioCapabilityType } from "./types";
type SurveyStatusFilter = "all" | "none" | "partial" | "complete";
@@ -36,6 +37,8 @@ interface DocumentTableProps {
data: ClassifiedDeal[];
onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void;
docStatusMap: DocStatusMap;
+ portfolioId: string;
+ userCapability: PortfolioCapabilityType;
}
function escapeCell(value: unknown): string {
@@ -49,7 +52,7 @@ function escapeCell(value: unknown): string {
: str;
}
-export default function DocumentTable({ data, onOpenDrawer, docStatusMap }: DocumentTableProps) {
+export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfolioId, userCapability }: DocumentTableProps) {
const [globalFilter, setGlobalFilter] = useState("");
const [surveyStatusFilter, setSurveyStatusFilter] = useState("all");
const [sorting, setSorting] = useState([]);
@@ -57,6 +60,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap }: Docu
pageIndex: 0,
pageSize: 25,
});
+ const [uploadDeal, setUploadDeal] = useState(null);
const filteredData = useMemo(() => {
if (surveyStatusFilter === "all") return data;
@@ -70,8 +74,12 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap }: Docu
}, [data, surveyStatusFilter, docStatusMap]);
const columns = useMemo(
- () => createDocumentTableColumns(onOpenDrawer, docStatusMap),
- [onOpenDrawer, docStatusMap],
+ () => createDocumentTableColumns(
+ onOpenDrawer,
+ docStatusMap,
+ userCapability === "contractor" ? setUploadDeal : undefined,
+ ),
+ [onOpenDrawer, docStatusMap, userCapability],
);
const table = useReactTable({
@@ -239,6 +247,15 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap }: Docu
+ {/* Contractor upload modal */}
+ {uploadDeal && (
+ setUploadDeal(null)}
+ />
+ )}
+
{/* Pagination */}
{pageCount > 1 && (
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
index f88514da..1b762c90 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
@@ -1,7 +1,7 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
-import { ArrowUpDown, CheckCircle2, AlertCircle, FileX } from "lucide-react";
+import { ArrowUpDown, CheckCircle2, AlertCircle, FileX, Upload } from "lucide-react";
import type { ClassifiedDeal, DocStatusMap, DocStatus } from "./types";
function SortableHeader({
@@ -50,6 +50,7 @@ function SurveyStatusBadge({ status }: { status: DocStatus | undefined }) {
export function createDocumentTableColumns(
onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void,
docStatusMap: DocStatusMap = {},
+ onUpload?: (deal: ClassifiedDeal) => void,
): ColumnDef
[] {
return [
// ── Address ──────────────────────────────────────────────────────────
@@ -143,5 +144,24 @@ export function createDocumentTableColumns(
enableSorting: false,
enableHiding: false,
},
+
+ // ── Upload button (contractor only) ──────────────────────────────────
+ ...(onUpload ? [{
+ id: "upload",
+ header: () => (
+ Upload
+ ),
+ cell: ({ row }: { row: { original: ClassifiedDeal } }) => (
+
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ } as ColumnDef] : []),
];
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx
deleted file mode 100644
index d7c83bf1..00000000
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/InstallUploadModal.tsx
+++ /dev/null
@@ -1,272 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogDescription,
- DialogFooter,
-} from "@/app/shadcn_components/ui/dialog";
-import { Button } from "@/app/shadcn_components/ui/button";
-import { Input } from "@/app/shadcn_components/ui/input";
-import {
- Select,
- SelectContent,
- SelectGroup,
- SelectItem,
- SelectLabel,
- SelectTrigger,
- SelectValue,
-} from "@/app/shadcn_components/ui/select";
-import { uploadFileToS3 } from "@/app/utils/s3";
-import type { ClassifiedDeal } from "./types";
-
-type Props = {
- deal: ClassifiedDeal;
- onClose: () => void;
-};
-
-// Contractor-specific file type options grouped for the UI
-const FILE_TYPE_OPTIONS: { value: string; label: string; group: string }[] = [
- // Install photos
- { value: "pre_photo", label: "Pre Photo", group: "Install Photos" },
- { value: "mid_photo", label: "Mid Photo", group: "Install Photos" },
- { value: "post_photo", label: "Post Photo", group: "Install Photos" },
- // Pre-installation
- { value: "pre_installation_building_inspection", label: "Pre-Installation Building Inspection (PIBI)", group: "Pre-Installation" },
- { value: "point_of_work_risk_assessment", label: "Point of Work Risk Assessment", group: "Pre-Installation" },
- // Compliance
- { value: "claim_of_compliance", label: "Claim of Compliance (PAS 2030)", group: "Compliance" },
- { value: "mcs_compliance_certificate", label: "MCS Compliance Certificate", group: "Compliance" },
- { value: "certificate_of_conformity", label: "Certificate of Conformity", group: "Compliance" },
- { value: "minor_works_electrical_certificate", label: "Minor Works Electrical Certificate", group: "Compliance" },
- // Handover
- { value: "handover_pack", label: "Handover Documents / Pack", group: "Handover" },
- { value: "workmanship_warranty", label: "Workmanship Warranty", group: "Handover" },
- { value: "insurance_guarantee", label: "Insurance Backed Guarantee (IBG)", group: "Handover" },
- { value: "g98_notification", label: "G98 / G99 Notification", group: "Handover" },
- { value: "ventilation_assessment_checklist", label: "Ventilation Assessment Checklist", group: "Handover" },
- // Qualifications
- { value: "installer_qualifications", label: "Installer Qualifications", group: "Qualifications" },
- { value: "installer_feedback", label: "Installer Feedback", group: "Other" },
- { value: "contractor_other", label: "Other", group: "Other" },
-];
-
-const FILE_TYPE_GROUPS = [
- "Install Photos",
- "Pre-Installation",
- "Compliance",
- "Handover",
- "Qualifications",
- "Other",
-];
-
-function parseMeasures(raw: string | null | undefined): string[] {
- if (!raw) return [];
- return raw.split(",").map((m) => m.trim()).filter(Boolean);
-}
-
-function contentTypeFor(ext: string): string {
- const e = ext.toLowerCase();
- if (e === "pdf") return "application/pdf";
- if (e === "xml") return "application/xml";
- if (["jpg", "jpeg"].includes(e)) return "image/jpeg";
- if (e === "png") return "image/png";
- return "application/octet-stream";
-}
-
-async function getPresignedUrl(path: string, contentType: string): Promise {
- const res = await fetch("/api/upload/retrofit-energy-assessments", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ path, contentType, expiresInSeconds: 300 }),
- });
- if (!res.ok) throw new Error("Failed to get presigned URL");
- const { url } = await res.json();
- return url;
-}
-
-async function recordUpload(payload: {
- s3FileKey: string;
- s3FileBucket: string;
- fileType: string;
- measureName?: string;
- uprn?: string;
- hubspotDealId?: string;
- landlordPropertyId?: string;
-}) {
- const res = await fetch("/api/upload/contractor-install", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(payload),
- });
- if (!res.ok) throw new Error("Failed to record upload");
-}
-
-export default function InstallUploadModal({ deal, onClose }: Props) {
- const measures = parseMeasures(deal.proposedMeasures);
- const [file, setFile] = useState(null);
- const [selectedMeasure, setSelectedMeasure] = useState(
- measures[0] ?? "",
- );
- const [selectedDocType, setSelectedDocType] = useState("");
- const [submitting, setSubmitting] = useState(false);
- const [error, setError] = useState(null);
-
- function handleFileChange(e: React.ChangeEvent) {
- const f = e.target.files?.[0] ?? null;
- setFile(f);
- setError(null);
- }
-
- async function handleUpload() {
- if (!file || !selectedDocType) return;
- setSubmitting(true);
- setError(null);
-
- try {
- const timestamp = new Date()
- .toISOString()
- .replace(/[-:]/g, "")
- .replace("T", "_")
- .split(".")[0];
- const ext = (file.name.split(".").pop() || "bin").toLowerCase();
- const ct = contentTypeFor(ext);
-
- const s3Key = `contractor-install/${deal.dealId}/${selectedDocType}/${timestamp}.${ext}`;
-
- const presignedUrl = await getPresignedUrl(s3Key, ct);
-
- await uploadFileToS3({ presignedUrl, file, contentType: ct });
-
- // Extract bucket from presigned URL (format: https://bucket.s3.region.amazonaws.com/...)
- const urlObj = new URL(presignedUrl);
- const bucket = urlObj.hostname.split(".")[0];
-
- await recordUpload({
- s3FileKey: s3Key,
- s3FileBucket: bucket,
- fileType: selectedDocType,
- measureName: selectedMeasure || undefined,
- uprn: deal.uprn ?? undefined,
- hubspotDealId: deal.dealId,
- landlordPropertyId: deal.landlordPropertyId ?? undefined,
- });
-
- onClose();
- } catch (err) {
- console.error(err);
- setError("Upload failed. Please try again.");
- } finally {
- setSubmitting(false);
- }
- }
-
- const canUpload = !!file && !!selectedDocType && !submitting;
-
- return (
-
- );
-}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
index a9138abf..2bb2a4f1 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
@@ -215,6 +215,8 @@ export default function LiveTracker({
data={currentProject?.allDeals ?? []}
onOpenDrawer={handleOpenDrawer}
docStatusMap={docStatusMap}
+ portfolioId={portfolioId}
+ userCapability={userCapability}
/>
@@ -356,6 +358,7 @@ export default function LiveTracker({
{/* ── Property detail drawer ─────────────────────────────────────── */}
setDetailDeal(null)}
/>
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
index 2aa61f5f..737aff26 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useMemo, useState } from "react";
-import { useMutation, useQueryClient } from "@tanstack/react-query";
+import React, { useMemo, useState } from "react";
+import { useMutation, useQuery } from "@tanstack/react-query";
import {
Table,
TableBody,
@@ -14,10 +14,20 @@ import { Input } from "@/app/shadcn_components/ui/input";
import { Button } from "@/app/shadcn_components/ui/button";
import { Badge } from "@/app/shadcn_components/ui/badge";
import { Checkbox } from "@/app/shadcn_components/ui/checkbox";
-import { Search, Upload, Save } from "lucide-react";
+import { Search, Save, ChevronDown, ChevronRight } from "lucide-react";
import { STAGE_COLORS } from "./types";
import type { ClassifiedDeal, PortfolioCapabilityType, ApprovalsByDeal } from "./types";
-import InstallUploadModal from "./InstallUploadModal";
+import { ApprovalConfirmDialog, type PendingDiff } from "./ApprovalConfirmDialog";
+
+type AuditEvent = {
+ id: string;
+ hubspotDealId: string;
+ measureName: string;
+ action: string; // 'approved' | 'unapproved'
+ actedByEmail: string;
+ actedByName: string | null;
+ actedAt: string; // ISO string
+};
type Props = {
data: ClassifiedDeal[];
@@ -28,10 +38,7 @@ type Props = {
function parseMeasures(raw: string | null | undefined): string[] {
if (!raw) return [];
- return raw
- .split(",")
- .map((m) => m.trim())
- .filter(Boolean);
+ return raw.split(",").map((m) => m.trim()).filter(Boolean);
}
function ApprovalStatus({
@@ -66,14 +73,83 @@ function ApprovalStatus({
);
}
-async function saveApprovals(
+function formatDate(iso: string) {
+ return new Date(iso).toLocaleString("en-GB", {
+ day: "numeric",
+ month: "short",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+}
+
+function ActivityLog({
+ dealId,
+ portfolioId,
+}: {
+ dealId: string;
+ portfolioId: string;
+}) {
+ const { data, isLoading } = useQuery<{ events: AuditEvent[] }>({
+ queryKey: ["approvalEvents", portfolioId, dealId],
+ queryFn: async () => {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/approvals?dealIds=${dealId}&include=events`,
+ );
+ if (!res.ok) throw new Error("Failed to fetch events");
+ return res.json();
+ },
+ staleTime: 30_000,
+ });
+
+ if (isLoading) {
+ return (
+ Loading activity…
+ );
+ }
+
+ const events = data?.events ?? [];
+
+ if (events.length === 0) {
+ return (
+ No activity yet.
+ );
+ }
+
+ return (
+
+ {events.map((e) => (
+
+
+ {e.action === "approved" ? "Approved" : "Unapproved"}
+
+ {e.measureName}
+ ·
+
+ {e.actedByName ?? e.actedByEmail}
+
+ ·
+ {formatDate(e.actedAt)}
+
+ ))}
+
+ );
+}
+
+async function postApprovalChanges(
portfolioId: string,
- deals: { hubspotDealId: string; approvedMeasures: string[] }[],
+ changes: { hubspotDealId: string; measureName: string; approved: boolean }[],
) {
const res = await fetch(`/api/portfolio/${portfolioId}/approvals`, {
method: "POST",
headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ deals }),
+ body: JSON.stringify({ changes }),
});
if (!res.ok) throw new Error("Failed to save approvals");
}
@@ -85,12 +161,14 @@ export default function MeasuresTable({
portfolioId,
}: Props) {
const [search, setSearch] = useState("");
+ // pendingChanges: dealId -> desired Set (the full intended approved set)
const [pendingChanges, setPendingChanges] = useState<
Record>
>({});
- const [uploadDeal, setUploadDeal] = useState(null);
const [savedApprovals, setSavedApprovals] =
useState(approvalsByDeal);
+ const [showConfirm, setShowConfirm] = useState(false);
+ const [expandedRows, setExpandedRows] = useState>(new Set());
// Filter to only properties with proposed measures
const dealsWithMeasures = useMemo(
@@ -111,56 +189,87 @@ export default function MeasuresTable({
const hasPendingChanges = Object.keys(pendingChanges).length > 0;
+ // Compute diffs: for each deal in pendingChanges, what's added vs removed vs saved
+ const pendingDiffs = useMemo>(() => {
+ const diffs: Record = {};
+ for (const [dealId, pending] of Object.entries(pendingChanges)) {
+ const saved = new Set(savedApprovals[dealId] ?? []);
+ const added = [...pending].filter((m) => !saved.has(m));
+ const removed = [...saved].filter((m) => !pending.has(m));
+ if (added.length > 0 || removed.length > 0) {
+ diffs[dealId] = { added, removed };
+ }
+ }
+ return diffs;
+ }, [pendingChanges, savedApprovals]);
+
+ const dealNames = useMemo>(() => {
+ const map: Record = {};
+ for (const d of dealsWithMeasures) {
+ map[d.dealId] = d.dealname ?? d.landlordPropertyId ?? d.dealId;
+ }
+ return map;
+ }, [dealsWithMeasures]);
+
const saveMutation = useMutation({
mutationFn: () => {
- const deals = Object.entries(pendingChanges).map(([dealId, measures]) => ({
- hubspotDealId: dealId,
- approvedMeasures: Array.from(measures),
- }));
- return saveApprovals(portfolioId, deals);
+ // Build flat list of explicit changes from diffs
+ const changes: { hubspotDealId: string; measureName: string; approved: boolean }[] = [];
+ for (const [dealId, diff] of Object.entries(pendingDiffs)) {
+ for (const m of diff.added) changes.push({ hubspotDealId: dealId, measureName: m, approved: true });
+ for (const m of diff.removed) changes.push({ hubspotDealId: dealId, measureName: m, approved: false });
+ }
+ return postApprovalChanges(portfolioId, changes);
},
onSuccess: () => {
- // Merge pending changes into saved approvals
setSavedApprovals((prev) => {
const next = { ...prev };
- for (const [dealId, measures] of Object.entries(pendingChanges)) {
- next[dealId] = Array.from(measures);
+ for (const [dealId, pending] of Object.entries(pendingChanges)) {
+ next[dealId] = Array.from(pending);
}
return next;
});
setPendingChanges({});
+ setShowConfirm(false);
},
});
- function toggleMeasure(dealId: string, measure: string, currentApproved: string[]) {
+ function toggleMeasure(dealId: string, measure: string) {
setPendingChanges((prev) => {
- // Start from either pending state or the saved state
- const current =
+ const base =
prev[dealId] !== undefined
? new Set(prev[dealId])
- : new Set(currentApproved);
+ : new Set(savedApprovals[dealId] ?? []);
- if (current.has(measure)) {
- current.delete(measure);
+ if (base.has(measure)) {
+ base.delete(measure);
} else {
- current.add(measure);
+ base.add(measure);
}
- // If the pending set equals the saved set, remove from pending
+ // If pending equals saved, remove from tracking
const saved = new Set(savedApprovals[dealId] ?? []);
- const setsEqual =
- current.size === saved.size && [...current].every((m) => saved.has(m));
+ const equal = base.size === saved.size && [...base].every((m) => saved.has(m));
const next = { ...prev };
- if (setsEqual) {
+ if (equal) {
delete next[dealId];
} else {
- next[dealId] = current;
+ next[dealId] = base;
}
return next;
});
}
+ function toggleRowExpand(dealId: string) {
+ setExpandedRows((prev) => {
+ const next = new Set(prev);
+ if (next.has(dealId)) next.delete(dealId);
+ else next.add(dealId);
+ return next;
+ });
+ }
+
if (dealsWithMeasures.length === 0) {
return (
@@ -191,14 +300,11 @@ export default function MeasuresTable({
{userCapability === "approver" && hasPendingChanges && (
)}
@@ -209,6 +315,7 @@ export default function MeasuresTable({
+
Address
@@ -221,11 +328,6 @@ export default function MeasuresTable({
Status
- {userCapability === "contractor" && (
-
- Actions
-
- )}
@@ -238,118 +340,130 @@ export default function MeasuresTable({
const approvedSet = new Set(approvedForDeal);
const stageColor = STAGE_COLORS[deal.displayStage];
const hasPending = pendingChanges[deal.dealId] !== undefined;
+ const isExpanded = expandedRows.has(deal.dealId);
return (
-
- {/* Address */}
-
-
- {deal.dealname ?? "—"}
-
- {deal.landlordPropertyId && (
-
- {deal.landlordPropertyId}
+
+
+ {/* Expand toggle */}
+
+
+
+
+ {/* Address */}
+
+
+ {deal.dealname ?? "—"}
- )}
-
+ {deal.landlordPropertyId && (
+
+ {deal.landlordPropertyId}
+
+ )}
+
- {/* Stage */}
-
-
+ {/* Stage */}
+
- {deal.displayStage}
-
-
+ className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs font-medium border ${stageColor.bg} ${stageColor.text} ${stageColor.border}`}
+ >
+
+ {deal.displayStage}
+
+
- {/* Proposed measures with approval checkboxes */}
-
-
- {proposed.map((measure) => {
- const isApproved = approvedSet.has(measure);
- if (userCapability === "approver") {
+ {/* Proposed measures */}
+
+
+ {proposed.map((measure) => {
+ const isApproved = approvedSet.has(measure);
+ if (userCapability === "approver") {
+ return (
+
+ );
+ }
return (
-
+
);
- }
- return (
-
- {measure}
-
- );
- })}
-
-
-
- {/* Status badge */}
-
-
-
-
- {/* Contractor upload button */}
- {userCapability === "contractor" && (
-
-
+ })}
+
+
+ {/* Status */}
+
+
+
+
+
+
+ {/* Expandable activity log row */}
+ {isExpanded && (
+
+
+
+
+
)}
-
+
);
})}
- {/* Install upload modal */}
- {uploadDeal && (
- setUploadDeal(null)}
- />
- )}
+ {/* Confirmation dialog */}
+ saveMutation.mutate()}
+ onCancel={() => setShowConfirm(false)}
+ isPending={saveMutation.isPending}
+ />
+
);
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDetailDrawer.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDetailDrawer.tsx
index cbe9c9d4..0ca08ce9 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDetailDrawer.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDetailDrawer.tsx
@@ -1,7 +1,9 @@
"use client";
+import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
import { motion, AnimatePresence } from "framer-motion";
-import { X, CheckCircle2, Circle, AlertTriangle } from "lucide-react";
+import { X, CheckCircle2, Circle, AlertTriangle, ChevronRight, ChevronDown } from "lucide-react";
import {
Drawer,
DrawerClose,
@@ -13,6 +15,84 @@ import {
import { STAGE_COLORS } from "./types";
import type { ClassifiedDeal } from "./types";
+// -----------------------------------------------------------------------
+// Approval log types + helpers
+// -----------------------------------------------------------------------
+type AuditEvent = {
+ id: string;
+ measureName: string;
+ action: string; // 'approved' | 'unapproved'
+ actedByEmail: string;
+ actedByName: string | null;
+ actedAt: string;
+};
+
+function formatDateTime(iso: string) {
+ return new Date(iso).toLocaleString("en-GB", {
+ day: "numeric",
+ month: "short",
+ year: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ });
+}
+
+function ApprovalLogSection({
+ dealId,
+ portfolioId,
+}: {
+ dealId: string;
+ portfolioId: string;
+}) {
+ const { data, isLoading } = useQuery<{ events: AuditEvent[] }>({
+ queryKey: ["approvalEvents", portfolioId, dealId],
+ queryFn: async () => {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/approvals?dealIds=${dealId}&include=events`,
+ );
+ if (!res.ok) throw new Error("Failed to fetch events");
+ return res.json();
+ },
+ staleTime: 30_000,
+ });
+
+ if (isLoading) {
+ return Loading activity…
;
+ }
+
+ const events = data?.events ?? [];
+
+ if (events.length === 0) {
+ return (
+ No approval activity yet.
+ );
+ }
+
+ return (
+
+ {events.map((e) => (
+
+
+ {e.action === "approved" ? "Approved" : "Unapproved"}
+
+
+
{e.measureName}
+
+ {e.actedByName ?? e.actedByEmail} · {formatDateTime(e.actedAt)}
+
+
+
+ ))}
+
+ );
+}
+
// -----------------------------------------------------------------------
// Milestone definitions — ordered pipeline steps with their date fields
// -----------------------------------------------------------------------
@@ -141,14 +221,16 @@ function MilestoneTimeline({ deal }: { deal: ClassifiedDeal }) {
// -----------------------------------------------------------------------
interface PropertyDetailDrawerProps {
deal: ClassifiedDeal | null;
+ portfolioId: string;
onClose: () => void;
}
-export default function PropertyDetailDrawer({ deal, onClose }: PropertyDetailDrawerProps) {
+export default function PropertyDetailDrawer({ deal, portfolioId, onClose }: PropertyDetailDrawerProps) {
const open = !!deal;
+ const [isLogOpen, setIsLogOpen] = useState(false);
return (
- !v && onClose()} direction="right">
+ { if (!v) { setIsLogOpen(false); onClose(); } }} direction="right">
@@ -255,6 +337,28 @@ export default function PropertyDetailDrawer({ deal, onClose }: PropertyDetailDr
Project Timeline
+
+ {/* Approval log — collapsible */}
+
+
+ {isLogOpen && (
+
+ )}
+
{/* Footer */}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index 1a8fc5ef..c91323e7 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -10,7 +10,7 @@ import { uploadedFiles } from "@/app/db/schema/uploaded_files";
import { portfolioOrganisation } from "@/app/db/schema/portfolio_organisation";
import { organisation } from "@/app/db/schema/organisation";
import { portfolioCapabilities } from "@/app/db/schema/portfolio";
-import { dealApprovals, dealApprovedMeasures } from "@/app/db/schema/approvals";
+import { dealMeasureApprovals } from "@/app/db/schema/approvals";
import { user as userTable } from "@/app/db/schema/users";
import type { HubspotDeal, DocStatusMap, DocStatus, PortfolioCapabilityType, ApprovalsByDeal } from "./types";
import { EXPECTED_SURVEY_DOC_TYPES } from "./types";
@@ -148,31 +148,25 @@ export default async function LiveReportingPage(props: {
}
}
- // Fetch deal approvals for all deals in scope
+ // Fetch currently approved measures for all deals in scope
const approvalsByDeal: ApprovalsByDeal = {};
const dealIds = deals.map((d) => d.dealId).filter(Boolean);
if (dealIds.length > 0) {
const approvalRows = await db
- .select({ id: dealApprovals.id, hubspotDealId: dealApprovals.hubspotDealId })
- .from(dealApprovals)
- .where(inArray(dealApprovals.hubspotDealId, dealIds));
+ .select({
+ hubspotDealId: dealMeasureApprovals.hubspotDealId,
+ measureName: dealMeasureApprovals.measureName,
+ })
+ .from(dealMeasureApprovals)
+ .where(
+ and(
+ inArray(dealMeasureApprovals.hubspotDealId, dealIds),
+ eq(dealMeasureApprovals.isApproved, true),
+ ),
+ );
- if (approvalRows.length > 0) {
- const approvalIds = approvalRows.map((a) => a.id);
- const measureRows = await db
- .select({
- dealApprovalId: dealApprovedMeasures.dealApprovalId,
- measureName: dealApprovedMeasures.measureName,
- })
- .from(dealApprovedMeasures)
- .where(inArray(dealApprovedMeasures.dealApprovalId, approvalIds));
-
- const approvalById = new Map(approvalRows.map((a) => [a.id.toString(), a.hubspotDealId]));
- for (const m of measureRows) {
- const dealId = approvalById.get(m.dealApprovalId.toString());
- if (!dealId) continue;
- (approvalsByDeal[dealId] ??= []).push(m.measureName);
- }
+ for (const row of approvalRows) {
+ (approvalsByDeal[row.hubspotDealId] ??= []).push(row.measureName);
}
}
From 33d1b42d913f0b6f1282e9cb35edb28b260afe8d Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Fri, 17 Apr 2026 10:00:13 +0000
Subject: [PATCH 10/43] add devcontainre
---
.devcontainer/devcontainer.json | 3 +-
devcontainer.sh | 102 ++++++++++++++++++++++++++++++++
2 files changed, 104 insertions(+), 1 deletion(-)
create mode 100644 devcontainer.sh
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index d1416d85..d8c62f05 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -5,7 +5,8 @@
"remoteUser": "vscode",
"workspaceFolder": "/workspaces/assessment-model",
"postStartCommand": "bash .devcontainer/post-install.sh",
- "forwardPorts": [3000],
+ "forwardPorts": [3000], # For vscode
+ "appPort": ["3000:3000"], # For devcontainer shell
"mounts": [
// Optional, just makes getting from Downloads (local env) easier
// "source=${localEnv:HOME},target=/workspaces/home,type=bind"
diff --git a/devcontainer.sh b/devcontainer.sh
new file mode 100644
index 00000000..5c536cb7
--- /dev/null
+++ b/devcontainer.sh
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+#
+# devcontainer.sh — devcontainer helper for this repo
+#
+# Usage:
+# ./devcontainer.sh
+#
+# Commands:
+# up build + start the devcontainer (idempotent)
+# shell attach a bash shell; auto-ups if not running
+# down stop the devcontainer
+# rebuild remove + rebuild from scratch, no cache
+#
+# Examples:
+# ./devcontainer.sh shell # one-shot: up if needed, then bash
+# ./devcontainer.sh rebuild
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
+REPO_ROOT="${SCRIPT_DIR}"
+CONFIG_PATH="${REPO_ROOT}/.devcontainer/devcontainer.json"
+
+VALID_COMMANDS=(up shell down rebuild)
+
+# --- helpers ---------------------------------------------------------------
+
+usage() {
+ sed -n '3,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
+ exit "${1:-0}"
+}
+
+die() {
+ echo "error: $*" >&2
+ exit 1
+}
+
+in_list() {
+ local needle="$1"
+ shift
+ local item
+ for item in "$@"; do
+ [[ "${item}" == "${needle}" ]] && return 0
+ done
+ return 1
+}
+
+container_id() {
+ # Find the running container for this repo via devcontainer labels.
+ docker ps -q \
+ --filter "label=devcontainer.local_folder=${REPO_ROOT}" \
+ --filter "label=devcontainer.config_file=${CONFIG_PATH}"
+}
+
+# --- argument parsing ------------------------------------------------------
+
+[[ $# -eq 1 ]] || usage 1
+
+COMMAND="$1"
+
+in_list "${COMMAND}" "${VALID_COMMANDS[@]}" \
+ || die "invalid command '${COMMAND}' (expected: ${VALID_COMMANDS[*]})"
+
+[[ -f "${CONFIG_PATH}" ]] || die "config not found: ${CONFIG_PATH}"
+
+DC_ARGS=(--workspace-folder "${REPO_ROOT}")
+
+# --- dispatch --------------------------------------------------------------
+
+case "${COMMAND}" in
+ up)
+ echo ">> bringing up devcontainer"
+ devcontainer up "${DC_ARGS[@]}"
+ ;;
+
+ shell)
+ # Auto-up if not already running. `devcontainer up` is idempotent —
+ # it reuses an existing container, so this is cheap on warm starts.
+ if [[ -z "$(container_id)" ]]; then
+ echo ">> devcontainer not running, bringing it up first"
+ devcontainer up "${DC_ARGS[@]}"
+ fi
+ echo ">> attaching shell"
+ devcontainer exec "${DC_ARGS[@]}" bash 2>/dev/null \
+ || devcontainer exec "${DC_ARGS[@]}" sh
+ ;;
+
+ down)
+ cid="$(container_id)"
+ if [[ -z "${cid}" ]]; then
+ echo ">> devcontainer not running, nothing to stop"
+ exit 0
+ fi
+ echo ">> stopping devcontainer"
+ docker stop "${cid}"
+ ;;
+
+ rebuild)
+ echo ">> rebuilding devcontainer from scratch"
+ devcontainer up "${DC_ARGS[@]}" --remove-existing-container --build-no-cache
+ ;;
+esac
From 02e1b527b890d5d67bd656c854a0bca58b7f0f80 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Fri, 17 Apr 2026 10:07:30 +0000
Subject: [PATCH 11/43] keep it minimal
---
.../portfolio/BulkUploadComingSoonModal.tsx | 87 +++++++++++++++++++
1 file changed, 87 insertions(+)
create mode 100644 src/app/components/portfolio/BulkUploadComingSoonModal.tsx
diff --git a/src/app/components/portfolio/BulkUploadComingSoonModal.tsx b/src/app/components/portfolio/BulkUploadComingSoonModal.tsx
new file mode 100644
index 00000000..2de2b428
--- /dev/null
+++ b/src/app/components/portfolio/BulkUploadComingSoonModal.tsx
@@ -0,0 +1,87 @@
+"use client";
+
+import {
+ Dialog,
+ DialogBackdrop,
+ DialogPanel,
+ Transition,
+ TransitionChild,
+} from "@headlessui/react";
+import { Fragment } from "react";
+import { XMarkIcon, RectangleStackIcon } from "@heroicons/react/24/outline";
+
+interface BulkUploadComingSoonModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ portfolioId: string;
+}
+
+export default function BulkUploadComingSoonModal({
+ isOpen,
+ onClose,
+}: BulkUploadComingSoonModalProps) {
+ return (
+
+
+
+ );
+}
From 31a1e46661de94c4b3205eab80478e5b3995ad4f Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Fri, 17 Apr 2026 11:57:54 +0000
Subject: [PATCH 12/43] tweaking documents and measures tables
---
.../your-projects/live/DocumentTable.tsx | 2 +-
.../your-projects/live/MeasuresTable.tsx | 4 ++--
.../[slug]/(portfolio)/your-projects/live/page.tsx | 13 +++++++------
.../[slug]/(portfolio)/your-projects/live/types.ts | 2 +-
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
index ac9b657a..195b534b 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
@@ -77,7 +77,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
() => createDocumentTableColumns(
onOpenDrawer,
docStatusMap,
- userCapability === "contractor" ? setUploadDeal : undefined,
+ userCapability.includes("contractor") ? setUploadDeal : undefined,
),
[onOpenDrawer, docStatusMap, userCapability],
);
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
index 737aff26..f7e3414a 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/MeasuresTable.tsx
@@ -297,7 +297,7 @@ export default function MeasuresTable({
{filtered.length} of {dealsWithMeasures.length} properties
- {userCapability === "approver" && hasPendingChanges && (
+ {userCapability.includes("approver") && hasPendingChanges && (
)}
- )}
+ );
+ })()}
@@ -659,6 +793,10 @@ export default function ContractorUploadModal({ deal, portfolioId, onClose }: Pr
Cancel
)}
+ {phase === "measure-select" && (
+ Cancel
+ )}
+
{phase === "upload" && (
<>
Cancel
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
index 01baed96..82656027 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
@@ -302,6 +302,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
deal={uploadDeal}
portfolioId={portfolioId}
onClose={() => setUploadDeal(null)}
+ docStatusMap={docStatusMap}
/>
)}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
index d630130e..2263d0d8 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
@@ -49,19 +49,38 @@ function RetroAssessmentBadge({ status }: { status: DocStatus | undefined }) {
function InstallDocsBadge({ status }: { status: DocStatus | undefined }) {
const installStatus = status?.installStatus ?? "none";
+ const measureProgress = status?.measureProgress ?? [];
+
+ // Build a tooltip showing per-measure doc counts (e.g. "ASHP: 5/9, CWI: 7/7")
+ const tooltip =
+ measureProgress.length > 0
+ ? measureProgress
+ .map((m) => `${m.measureName}: ${m.uploadedCount}/${m.requiredCount}`)
+ .join(" | ")
+ : undefined;
+
if (installStatus === "all") {
return (
-
+
All Measures
);
}
if (installStatus === "partial") {
+ const completedCount = measureProgress.filter((m) => m.isComplete).length;
+ const totalCount = measureProgress.length;
+ const label = totalCount > 0 ? `${completedCount} / ${totalCount} measures` : "Some Measures";
return (
-
+
- Some Measures
+ {label}
);
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index fb2cf17c..ff958a0e 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -13,8 +13,9 @@ import { portfolioCapabilities, portfolioUsers } from "@/app/db/schema/portfolio
import { dealMeasureApprovals } from "@/app/db/schema/approvals";
import { propertyRemovalRequests } from "@/app/db/schema/removal_requests";
import { user as userTable } from "@/app/db/schema/users";
-import type { HubspotDeal, DocStatusMap, DocStatus, PortfolioCapabilityType, ApprovalsByDeal, RemovalStatusByDeal, EffectiveRemovalState } from "./types";
+import type { HubspotDeal, DocStatusMap, DocStatus, MeasureDocProgress, PortfolioCapabilityType, ApprovalsByDeal, RemovalStatusByDeal, EffectiveRemovalState } from "./types";
import { EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES } from "./types";
+import { getRequiredDocs } from "@/app/lib/measureDocumentRequirements";
import type { InferSelectModel } from "drizzle-orm";
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
import { Building2 } from "lucide-react";
@@ -271,15 +272,33 @@ export default async function LiveReportingPage(props: {
const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType));
const measures = measuresByUprn.get(uprn) ?? [];
+
+ // Compute per-measure document progress against the requirements matrix
+ const measureProgress: MeasureDocProgress[] = measures.map((measureName) => {
+ const required = getRequiredDocs(measureName);
+ const docsForMeasure = installDocs.filter((d) => d.measureName === measureName);
+ const uploadedTypeSet = new Set(docsForMeasure.map((d) => d.fileType));
+ const uploaded = required.filter((r) => uploadedTypeSet.has(r));
+ return {
+ measureName,
+ required,
+ uploaded,
+ isComplete: uploaded.length === required.length,
+ uploadedCount: uploaded.length,
+ requiredCount: required.length,
+ };
+ });
+
let installStatus: DocStatus["installStatus"] = "none";
if (installDocs.length > 0) {
if (measures.length === 0) {
installStatus = "hasDocs";
} else {
- const measuresWithDocs = new Set(
- installDocs.map((d) => d.measureName).filter(Boolean),
- );
- installStatus = measures.every((m) => measuresWithDocs.has(m)) ? "all" : "partial";
+ installStatus = measureProgress.every((m) => m.isComplete)
+ ? "all"
+ : measureProgress.some((m) => m.uploadedCount > 0)
+ ? "partial"
+ : "none";
}
}
@@ -289,6 +308,7 @@ export default async function LiveReportingPage(props: {
isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) => surveyTypeSet.has(t)),
hasInstallDocs: installDocs.length > 0,
installStatus,
+ measureProgress,
};
docStatusMap[uprn] = status;
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
index 987ea3c8..c4a070b2 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
@@ -250,6 +250,16 @@ export const SURVEY_ALL_DOC_TYPES = new Set([
"ecmk_survey_xml",
]);
+// Per-measure document upload progress
+export type MeasureDocProgress = {
+ measureName: string;
+ required: string[]; // required fileType values for this measure
+ uploaded: string[]; // required fileType values that have been uploaded
+ isComplete: boolean;
+ uploadedCount: number;
+ requiredCount: number;
+};
+
export type DocStatus = {
// Retrofit assessment docs
presentSurveyTypes: string[];
@@ -258,10 +268,11 @@ export type DocStatus = {
// Install docs
hasInstallDocs: boolean;
installStatus: "none" | "partial" | "hasDocs" | "all";
- // "all" = install docs exist for every proposed measure
- // "partial" = some (but not all) proposed measures have docs
+ // "all" = all required docs uploaded for every proposed measure
+ // "partial" = some (but not all) proposed measures have complete docs
// "hasDocs" = has install docs but no measures defined on the deal
// "none" = no install docs at all
+ measureProgress: MeasureDocProgress[]; // one entry per proposed measure
};
export type DocStatusMap = Record; // keyed by UPRN string
From 2536f3eb8e9115dd3cb2837b8b44dd4c9a5543aa Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Mon, 20 Apr 2026 16:38:20 +0000
Subject: [PATCH 38/43] updated the UI to manage document uploads down to
measure level
---
.../live/ContractorUploadModal.tsx | 297 ++++++++++++------
.../your-projects/live/DocumentTable.tsx | 6 +-
.../your-projects/live/LiveTracker.tsx | 2 +
.../your-projects/live/PropertyDrawer.tsx | 149 +++++++--
.../(portfolio)/your-projects/live/page.tsx | 8 +-
5 files changed, 346 insertions(+), 116 deletions(-)
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
index 4b86fa35..95c60e75 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
@@ -52,6 +52,7 @@ type Props = {
portfolioId: string;
onClose: () => void;
docStatusMap?: DocStatusMap;
+ approvedMeasures?: string[]; // if non-empty, used instead of proposedMeasures
};
// ── Constants ─────────────────────────────────────────────────────────────
@@ -261,29 +262,122 @@ function PasGuidancePanel() {
// ── DocType select ────────────────────────────────────────────────────────
-function DocTypeSelect({
+// ── DocType button grid — shown when a measure is selected ───────────────
+
+function DocTypeButtonGrid({
value,
onChange,
- showHint = false,
requiredDocs,
uploadedDocs,
}: {
value: string;
onChange: (v: string) => void;
- showHint?: boolean;
- requiredDocs?: string[]; // file types required for the selected measure
- uploadedDocs?: string[]; // file types already uploaded for the selected measure
+ requiredDocs: string[];
+ uploadedDocs: string[];
+}) {
+ const [showOther, setShowOther] = useState(false);
+ const uploadedSet = new Set(uploadedDocs);
+ const requiredSet = new Set(requiredDocs);
+ const isOtherSelected = value !== "" && !requiredSet.has(value);
+
+ return (
+
+ {/* Required doc type buttons */}
+
+ {requiredDocs.map((docType) => {
+ const option = FILE_TYPE_OPTIONS.find((o) => o.value === docType);
+ const label = option?.label ?? docType;
+ const alreadyUploaded = uploadedSet.has(docType);
+ const isSelected = value === docType;
+
+ return (
+
{ onChange(docType); setShowOther(false); }}
+ title={alreadyUploaded ? `${label} — already uploaded` : label}
+ className={`inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg border text-xs font-medium transition-all duration-100 ${
+ isSelected
+ ? "bg-brandblue text-white border-brandblue shadow-sm"
+ : alreadyUploaded
+ ? "bg-emerald-50 text-emerald-700 border-emerald-200 hover:border-emerald-400"
+ : "bg-white text-gray-700 border-gray-200 hover:border-brandblue/50 hover:bg-brandlightblue/10"
+ }`}
+ >
+ {alreadyUploaded && !isSelected && (
+
+ )}
+ {label}
+
+ );
+ })}
+
+ {/* Other button */}
+
setShowOther((v) => !v)}
+ className={`inline-flex items-center gap-1 px-2.5 py-1.5 rounded-lg border text-xs font-medium transition-all duration-100 ${
+ isOtherSelected || showOther
+ ? "bg-gray-100 text-gray-700 border-gray-300"
+ : "bg-white text-gray-400 border-gray-200 hover:border-gray-400 hover:text-gray-600"
+ }`}
+ >
+ Other {showOther ? "▲" : "▼"}
+
+
+
+ {/* Other: dropdown for non-required types */}
+ {(showOther || isOtherSelected) && (
+
+ )}
+
+ {/* Show hint for selected type */}
+ {value && (() => {
+ const hint = FILE_TYPE_OPTIONS.find((o) => o.value === value)?.hint;
+ return hint ?
{hint}
: null;
+ })()}
+
+ );
+}
+
+// ── DocType select — fallback when no measure selected ────────────────────
+
+function DocTypeSelect({
+ value,
+ onChange,
+}: {
+ value: string;
+ onChange: (v: string) => void;
}) {
const selected = FILE_TYPE_OPTIONS.find((o) => o.value === value);
- const requiredSet = new Set(requiredDocs ?? []);
- const uploadedSet = new Set(uploadedDocs ?? []);
-
- // If we have required docs, show them as a priority group first
- const priorityItems = requiredDocs && requiredDocs.length > 0
- ? requiredDocs
- .map((t) => FILE_TYPE_OPTIONS.find((o) => o.value === t))
- .filter((o): o is typeof FILE_TYPE_OPTIONS[number] => !!o)
- : [];
return (
@@ -293,32 +387,8 @@ function DocTypeSelect({
Select type…
-
- {/* Priority group: required docs for the selected measure */}
- {priorityItems.length > 0 && (
-
-
- Required for this measure
-
- {priorityItems.map((o) => {
- const alreadyUploaded = uploadedSet.has(o.value);
- return (
-
- {o.label}
- {alreadyUploaded && (
- ✓ uploaded
- )}
-
- );
- })}
-
- )}
-
- {/* Remaining groups */}
{FILE_TYPE_GROUPS.map((group) => {
- const items = FILE_TYPE_OPTIONS.filter(
- (o) => o.group === group && !requiredSet.has(o.value),
- );
+ const items = FILE_TYPE_OPTIONS.filter((o) => o.group === group);
if (!items.length) return null;
return (
@@ -326,16 +396,14 @@ function DocTypeSelect({
{group}
{items.map((o) => (
-
- {o.label}
-
+ {o.label}
))}
);
})}
- {showHint && selected?.hint && (
+ {selected?.hint && (
{selected.hint}
)}
@@ -354,8 +422,11 @@ function StatusIcon({ status, isExisting, errorMsg }: { status: FileStatus; isEx
// ── Main component ─────────────────────────────────────────────────────────
-export default function ContractorUploadModal({ deal, portfolioId, onClose, docStatusMap }: Props) {
- const measures = parseMeasures(deal.proposedMeasures);
+export default function ContractorUploadModal({ deal, portfolioId, onClose, docStatusMap, approvedMeasures }: Props) {
+ // Use approved measures when available; fall back to all proposed measures
+ const measures = (approvedMeasures && approvedMeasures.length > 0)
+ ? approvedMeasures
+ : parseMeasures(deal.proposedMeasures);
const fileInputRef = useRef(null);
const [isDragOver, setIsDragOver] = useState(false);
const [queue, setQueue] = useState([]);
@@ -708,63 +779,107 @@ export default function ContractorUploadModal({ deal, portfolioId, onClose, docS
{/* ── Phase 2: Classify ── */}
{phase === "classify" && (() => {
- // Per-entry: look up what's already uploaded for that entry's measure
const uprn = deal.uprn ?? null;
const docStatus = uprn ? docStatusMap?.[uprn] : undefined;
const measureProgressMap = new Map(
(docStatus?.measureProgress ?? []).map((m) => [m.measureName, m]),
);
return (
-
+
{/* PAS guidance */}
- {/* Column headers */}
-
- File
- Document Type *
- Measure
-
-
- {classifiableEntries.map((entry) => {
- const entryMeasure = entry.measureName && entry.measureName !== "__none__" ? entry.measureName : null;
- const requiredDocs = entryMeasure ? getRequiredDocs(entryMeasure) : undefined;
- const uploadedDocs = entryMeasure ? (measureProgressMap.get(entryMeasure)?.uploaded ?? []) : undefined;
- return (
-
-
-
-
-
{entry.displayName}
- {entry.displaySize &&
{entry.displaySize}
}
- {entry.existingS3Key &&
Previously uploaded
}
-
+ {/* Measure context banner */}
+ {selectedMeasure && (
+
+
+ {selectedMeasure}
+ {(() => {
+ const mp = measureProgressMap.get(selectedMeasure);
+ if (!mp) return null;
+ return (
+
+ {mp.uploadedCount}/{mp.requiredCount} docs uploaded
+
+ );
+ })()}
-
updateEntryField(entry.id, "docType", v)}
- showHint
- requiredDocs={requiredDocs}
- uploadedDocs={uploadedDocs}
- />
- {measures.length > 0 ? (
-
- ) : (
- —
- )}
+ setPhase("measure-select")}
+ className="text-[10px] text-brandblue/60 hover:text-brandblue underline underline-offset-2"
+ >
+ Change
+
- );
- })}
+ )}
+
+ {/* File list with classification */}
+
+ {classifiableEntries.map((entry) => {
+ const entryMeasure = entry.measureName && entry.measureName !== "__none__" ? entry.measureName : null;
+ const requiredDocs = entryMeasure ? getRequiredDocs(entryMeasure) : null;
+ const uploadedDocs = entryMeasure ? (measureProgressMap.get(entryMeasure)?.uploaded ?? []) : [];
+
+ return (
+
+ {/* File info row */}
+
+
+
+
{entry.displayName}
+
+ {entry.existingS3Key ? "Previously uploaded · " : ""}
+ {entry.displaySize ?? ""}
+ {entryMeasure && !selectedMeasure && (
+ {entryMeasure}
+ )}
+
+
+ {/* Measure selector — only shown if no pre-selected measure */}
+ {!selectedMeasure && measures.length > 0 && (
+
+ )}
+
+
+ {/* Doc type selector */}
+
+
+ Document type *
+
+ {requiredDocs ? (
+
updateEntryField(entry.id, "docType", v)}
+ requiredDocs={requiredDocs}
+ uploadedDocs={uploadedDocs}
+ />
+ ) : (
+ updateEntryField(entry.id, "docType", v)}
+ />
+ )}
+
+
+ );
+ })}
+
{/* Failed uploads (info only) */}
{queue.filter((f) => f.status === "error").length > 0 && (
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
index 82656027..26457b9e 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
@@ -29,7 +29,7 @@ import {
import { Search, ChevronLeft, ChevronRight, Download } from "lucide-react";
import { createDocumentTableColumns } from "./DocumentTableColumns";
import ContractorUploadModal from "./ContractorUploadModal";
-import type { ClassifiedDeal, DocStatusMap, PortfolioCapabilityType } from "./types";
+import type { ClassifiedDeal, DocStatusMap, PortfolioCapabilityType, ApprovalsByDeal } from "./types";
type RetroAssessmentFilter = "all" | "none" | "partial" | "complete";
type InstallStatusFilter = "all" | "none" | "hasDocs" | "partial" | "complete";
@@ -40,6 +40,7 @@ interface DocumentTableProps {
docStatusMap: DocStatusMap;
portfolioId: string;
userCapability: PortfolioCapabilityType;
+ approvalsByDeal?: ApprovalsByDeal;
}
function escapeCell(value: unknown): string {
@@ -53,7 +54,7 @@ function escapeCell(value: unknown): string {
: str;
}
-export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfolioId, userCapability }: DocumentTableProps) {
+export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfolioId, userCapability, approvalsByDeal }: DocumentTableProps) {
const [globalFilter, setGlobalFilter] = useState("");
const [retroAssessmentFilter, setRetroAssessmentFilter] = useState
("all");
const [installStatusFilter, setInstallStatusFilter] = useState("all");
@@ -303,6 +304,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
portfolioId={portfolioId}
onClose={() => setUploadDeal(null)}
docStatusMap={docStatusMap}
+ approvedMeasures={approvalsByDeal?.[uploadDeal.dealId] ?? []}
/>
)}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
index 8dd230f4..8f73aad7 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
@@ -243,6 +243,7 @@ export default function LiveTracker({
docStatusMap={docStatusMap}
portfolioId={portfolioId}
userCapability={userCapability}
+ approvalsByDeal={approvalsByDeal}
/>
@@ -376,6 +377,7 @@ export default function LiveTracker({
uprn={drawerState.uprn}
landlordPropertyId={drawerState.landlordPropertyId}
dealname={drawerState.dealname}
+ docStatus={drawerState.uprn ? docStatusMap[drawerState.uprn] : undefined}
onClose={() =>
setDrawerState({ open: false, uprn: null, landlordPropertyId: null, dealname: null })
}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx
index 63ef1313..c40099b8 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx
@@ -21,7 +21,7 @@ import {
DrawerTitle,
DrawerDescription,
} from "@/app/shadcn_components/ui/drawer";
-import type { PropertyDocument } from "./types";
+import type { PropertyDocument, DocStatus } from "./types";
import { EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES, SURVEY_ALL_DOC_TYPES } from "./types";
// Human-readable labels for all DB fileType enum values
@@ -86,11 +86,9 @@ function formatDate(iso: string): string {
}
// -----------------------------------------------------------------------
-// Individual document row
+// Reusable download button — encapsulates the presigned URL mutation
// -----------------------------------------------------------------------
-function DocumentRow({ doc, showMeasure }: { doc: PropertyDocument; showMeasure?: boolean }) {
- const label = DOC_TYPE_LABELS[doc.docType] ?? doc.docType;
-
+function DownloadDocButton({ doc }: { doc: PropertyDocument }) {
const { mutate: download, isPending: signing } = useMutation({
mutationFn: async () => {
const res = await fetch("/api/sign-document-url", {
@@ -107,6 +105,28 @@ function DocumentRow({ doc, showMeasure }: { doc: PropertyDocument; showMeasure?
},
});
+ return (
+
download()}
+ disabled={signing}
+ className="shrink-0 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-brandblue text-white text-xs font-medium hover:bg-brandblue/90 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
+ >
+ {signing ? (
+
+ ) : (
+
+ )}
+ {signing ? "Preparing…" : "Download"}
+
+ );
+}
+
+// -----------------------------------------------------------------------
+// Individual document row — used in retrofit section and install fallback
+// -----------------------------------------------------------------------
+function DocumentRow({ doc, showMeasure }: { doc: PropertyDocument; showMeasure?: boolean }) {
+ const label = DOC_TYPE_LABELS[doc.docType] ?? doc.docType;
+
return (
- {/* Right: download button */}
-
download()}
- disabled={signing}
- className="shrink-0 inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-brandblue text-white text-xs font-medium hover:bg-brandblue/90 transition-colors disabled:opacity-60 disabled:cursor-not-allowed"
- >
- {signing ? (
-
- ) : (
-
- )}
- {signing ? "Preparing…" : "Download"}
-
+
);
}
@@ -155,6 +163,7 @@ interface PropertyDrawerProps {
uprn: string | null;
landlordPropertyId: string | null;
dealname: string | null;
+ docStatus?: DocStatus;
onClose: () => void;
}
@@ -163,6 +172,7 @@ export default function PropertyDrawer({
uprn,
landlordPropertyId,
dealname,
+ docStatus,
onClose,
}: PropertyDrawerProps) {
const canQuery = !!(uprn || landlordPropertyId);
@@ -361,13 +371,112 @@ export default function PropertyDrawer({
key="install"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
- className="space-y-2"
+ className="space-y-3"
>
Install Documents
- {installDocs.length > 0 ? (
+
+ {docStatus?.measureProgress && docStatus.measureProgress.length > 0 ? (
+ // ── Per-measure checklist ──
+
+ {docStatus.measureProgress.map((mp) => {
+ const measureDocs = installDocs.filter((d) => d.measureName === mp.measureName);
+ const uploadedTypeSet = new Set(measureDocs.map((d) => d.docType));
+ const missingTypes = mp.required.filter((t) => !uploadedTypeSet.has(t));
+
+ return (
+
+ {/* Measure header */}
+
+ {mp.measureName}
+ 0
+ ? "bg-amber-50 text-amber-700 border-amber-200"
+ : "bg-gray-100 text-gray-500 border-gray-200"
+ }`}>
+ {mp.uploadedCount} / {mp.requiredCount} docs
+
+
+
+
+ {/* Uploaded required docs */}
+ {mp.uploaded.map((docType) => {
+ const doc = measureDocs.find((d) => d.docType === docType);
+ if (!doc) return null;
+ return (
+
+
+
+
+
{DOC_TYPE_LABELS[docType] ?? docType}
+
{formatDate(doc.s3UploadTimestamp)}
+
+
+
+
+ );
+ })}
+
+ {/* Missing required docs */}
+ {missingTypes.map((docType) => (
+
+
+
+
+
{DOC_TYPE_LABELS[docType] ?? docType}
+
+ ))}
+
+ {/* Extra docs uploaded for this measure (not in required list) */}
+ {measureDocs
+ .filter((d) => !mp.required.includes(d.docType))
+ .map((doc) => (
+
+
+
+
+
+
+
{DOC_TYPE_LABELS[doc.docType] ?? doc.docType}
+
{formatDate(doc.s3UploadTimestamp)}
+
+
+
+
+ ))
+ }
+
+
+ );
+ })}
+
+ {/* Unassigned / no-measure install docs */}
+ {(() => {
+ const knownMeasures = new Set(docStatus.measureProgress.map((m) => m.measureName));
+ const unassigned = installDocs.filter(
+ (d) => !d.measureName || !knownMeasures.has(d.measureName),
+ );
+ if (unassigned.length === 0) return null;
+ return (
+
+
Other
+ {unassigned.map((doc) => (
+
+ ))}
+
+ );
+ })()}
+
+ ) : installDocs.length > 0 ? (
+ // ── Fallback: flat list (no measure progress data) ──
{installDocs.map((doc) => (
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index ff958a0e..1b659b5c 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -255,13 +255,15 @@ export default async function LiveReportingPage(props: {
docsByUprn.get(key)!.push({ fileType: row.fileType, measureName: row.measureName });
}
- // Build measures lookup from deals (uprn → proposed measure names)
+ // Build measures lookup from deals (uprn → approved measures, falling back to proposed)
const measuresByUprn = new Map
();
for (const deal of deals) {
if (deal.uprn) {
const key = String(deal.uprn);
- const measures = (deal.proposedMeasures ?? "")
- .split(",").map((m: string) => m.trim()).filter(Boolean);
+ const approved = approvalsByDeal[deal.dealId] ?? [];
+ const measures = approved.length > 0
+ ? approved
+ : (deal.proposedMeasures ?? "").split(",").map((m: string) => m.trim()).filter(Boolean);
measuresByUprn.set(key, measures);
}
}
From a0b12673f3eaf179a49919d6a0256542e7550eee Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Mon, 20 Apr 2026 16:49:06 +0000
Subject: [PATCH 39/43] changed documents management to documents
---
.../your-projects/live/LiveTracker.tsx | 67 ++++++++++++++-----
1 file changed, 52 insertions(+), 15 deletions(-)
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
index 8f73aad7..824a1cbf 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
@@ -9,7 +9,13 @@ import {
TabsTrigger,
} from "@/app/shadcn_components/ui/tabs";
import { Card, CardContent } from "@/app/shadcn_components/ui/card";
-import { BarChart2, Table2, FolderOpen, Wrench, AlertTriangle } from "lucide-react";
+import {
+ BarChart2,
+ Table2,
+ FolderOpen,
+ Wrench,
+ AlertTriangle,
+} from "lucide-react";
import DrillDownTable from "./DrillDownTable";
import PropertyTable from "./PropertyTable";
import DocumentTable from "./DocumentTable";
@@ -40,9 +46,9 @@ export default function LiveTracker({
userEmail,
}: LiveTrackerProps) {
// ── Tab state ────────────────────────────────────────────────────────
- const [activeTab, setActiveTab] = useState<"analytics" | "properties" | "documents" | "measures">(
- "analytics",
- );
+ const [activeTab, setActiveTab] = useState<
+ "analytics" | "properties" | "documents" | "measures"
+ >("analytics");
// ── Project selector (shared across both tabs) ───────────────────────
const projectCodes = projects.map((p) => p.projectCode);
@@ -81,7 +87,10 @@ export default function LiveTracker({
setOpenTable({
stage,
data: filteredDeals,
- columns: (columns || ["dealname", "landlordPropertyId"]) as (keyof ClassifiedDeal)[],
+ columns: (columns || [
+ "dealname",
+ "landlordPropertyId",
+ ]) as (keyof ClassifiedDeal)[],
columnLabels: (columnLabels || {
dealname: "Address Ref.",
landlordPropertyId: "Property Ref.",
@@ -90,7 +99,11 @@ export default function LiveTracker({
});
};
- const handleOpenDrawer = (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => {
+ const handleOpenDrawer = (
+ uprn: string | null,
+ landlordPropertyId: string | null,
+ dealname: string | null,
+ ) => {
setDrawerState({ open: true, uprn, landlordPropertyId, dealname });
};
@@ -108,7 +121,11 @@ export default function LiveTracker({
setActiveTab(v as "analytics" | "properties" | "documents" | "measures")}
+ onValueChange={(v) =>
+ setActiveTab(
+ v as "analytics" | "properties" | "documents" | "measures",
+ )
+ }
>
{/* Tab bar */}
@@ -127,7 +144,9 @@ export default function LiveTracker({
Properties
0 ? "opacity-100" : "opacity-0 pointer-events-none"
+ pendingRemovalCount > 0
+ ? "opacity-100"
+ : "opacity-0 pointer-events-none"
}`}
aria-hidden={pendingRemovalCount === 0}
>
@@ -139,7 +158,7 @@ export default function LiveTracker({
className="flex items-center gap-2 rounded-lg text-sm font-medium px-4 data-[state=active]:bg-white data-[state=active]:text-brandblue data-[state=active]:shadow-sm transition-all"
>
- Document Management
+ Documents
)}
-
+
{pendingRemovalCount}{" "}
- {pendingRemovalCount === 1 ? "property has" : "properties have"} an outstanding removal request
+ {pendingRemovalCount === 1 ? "property has" : "properties have"}{" "}
+ an outstanding removal request
{projectCodes.map((code) =>
code === "__ALL__" ? (
-
) : (
@@ -261,7 +287,11 @@ export default function LiveTracker({
>
{projectCodes.map((code) =>
code === "__ALL__" ? (
-
) : (
@@ -377,9 +407,16 @@ export default function LiveTracker({
uprn={drawerState.uprn}
landlordPropertyId={drawerState.landlordPropertyId}
dealname={drawerState.dealname}
- docStatus={drawerState.uprn ? docStatusMap[drawerState.uprn] : undefined}
+ docStatus={
+ drawerState.uprn ? docStatusMap[drawerState.uprn] : undefined
+ }
onClose={() =>
- setDrawerState({ open: false, uprn: null, landlordPropertyId: null, dealname: null })
+ setDrawerState({
+ open: false,
+ uprn: null,
+ landlordPropertyId: null,
+ dealname: null,
+ })
}
/>
From c7a57808774b37e18d87d051ddc6f8a9befb8636 Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Mon, 20 Apr 2026 18:58:24 +0000
Subject: [PATCH 40/43] implemeting document search on deal id instead of uprn
---
.../live-tracking/property-documents/route.ts | 17 +-
.../live/ContractorUploadModal.tsx | 6 +-
.../your-projects/live/DocumentTable.tsx | 6 +-
.../live/DocumentTableColumns.tsx | 14 +-
.../your-projects/live/LiveTracker.tsx | 8 +-
.../your-projects/live/PropertyDrawer.tsx | 9 +-
.../your-projects/live/PropertyTable.tsx | 4 +-
.../live/PropertyTableColumns.tsx | 9 +-
.../(portfolio)/your-projects/live/page.tsx | 165 ++++++++++--------
.../(portfolio)/your-projects/live/types.ts | 3 +-
10 files changed, 138 insertions(+), 103 deletions(-)
diff --git a/src/app/api/live-tracking/property-documents/route.ts b/src/app/api/live-tracking/property-documents/route.ts
index 55774d8a..35f49915 100644
--- a/src/app/api/live-tracking/property-documents/route.ts
+++ b/src/app/api/live-tracking/property-documents/route.ts
@@ -5,22 +5,25 @@ import { uploadedFiles } from "@/app/db/schema/uploaded_files";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
+ const dealIdParam = searchParams.get("dealId");
const uprnParam = searchParams.get("uprn");
const landlordPropertyIdParam = searchParams.get("landlordPropertyId");
- if (!uprnParam && !landlordPropertyIdParam) {
+ if (!dealIdParam && !uprnParam && !landlordPropertyIdParam) {
return NextResponse.json(
- { error: "uprn or landlordPropertyId is required" },
+ { error: "dealId, uprn, or landlordPropertyId is required" },
{ status: 400 },
);
}
try {
- // Prefer UPRN — it's more selective and avoids an OR full-table scan.
- // Only fall back to landlordPropertyId when no UPRN is available.
- const condition = uprnParam
- ? eq(uploadedFiles.uprn, BigInt(uprnParam))
- : eq(uploadedFiles.landlordPropertyId, landlordPropertyIdParam!);
+ // Prefer dealId — reliable even when UPRN is missing from the deal.
+ // Fall back to UPRN, then landlordPropertyId.
+ const condition = dealIdParam
+ ? eq(uploadedFiles.hubsotDealId, dealIdParam)
+ : uprnParam
+ ? eq(uploadedFiles.uprn, BigInt(uprnParam))
+ : eq(uploadedFiles.landlordPropertyId, landlordPropertyIdParam!);
const rows = await db
.select({
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
index 95c60e75..ccb1db8c 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/ContractorUploadModal.tsx
@@ -663,8 +663,7 @@ export default function ContractorUploadModal({ deal, portfolioId, onClose, docS
{/* ── Measure Select ── */}
{phase === "measure-select" && (() => {
- const uprn = deal.uprn ?? null;
- const docStatus = uprn ? docStatusMap?.[uprn] : undefined;
+ const docStatus = docStatusMap?.[deal.dealId];
const measureProgressMap = new Map(
(docStatus?.measureProgress ?? []).map((m) => [m.measureName, m]),
);
@@ -779,8 +778,7 @@ export default function ContractorUploadModal({ deal, portfolioId, onClose, docS
{/* ── Phase 2: Classify ── */}
{phase === "classify" && (() => {
- const uprn = deal.uprn ?? null;
- const docStatus = uprn ? docStatusMap?.[uprn] : undefined;
+ const docStatus = docStatusMap?.[deal.dealId];
const measureProgressMap = new Map(
(docStatus?.measureProgress ?? []).map((m) => [m.measureName, m]),
);
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
index 26457b9e..8efbcb81 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTable.tsx
@@ -36,7 +36,7 @@ type InstallStatusFilter = "all" | "none" | "hasDocs" | "partial" | "complete";
interface DocumentTableProps {
data: ClassifiedDeal[];
- onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void;
+ onOpenDrawer: (dealId: string, uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void;
docStatusMap: DocStatusMap;
portfolioId: string;
userCapability: PortfolioCapabilityType;
@@ -67,7 +67,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
const filteredData = useMemo(() => {
return data.filter((d) => {
- const status = d.uprn ? docStatusMap[d.uprn] : undefined;
+ const status = docStatusMap[d.dealId];
if (retroAssessmentFilter !== "all") {
if (retroAssessmentFilter === "none" && !(!status || !status.hasSurveyDocs)) return false;
@@ -115,7 +115,7 @@ export default function DocumentTable({ data, onOpenDrawer, docStatusMap, portfo
const header = "Address,Landlord ID,Retrofit Assessment Status,Install Docs Status";
const body = rows
.map((row) => {
- const status = row.original.uprn ? docStatusMap[row.original.uprn] : undefined;
+ const status = docStatusMap[row.original.dealId];
const retroStatus = status?.isSurveyComplete
? "Complete"
: status?.hasSurveyDocs
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
index 2263d0d8..f1371e79 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DocumentTableColumns.tsx
@@ -101,7 +101,7 @@ function InstallDocsBadge({ status }: { status: DocStatus | undefined }) {
}
export function createDocumentTableColumns(
- onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void,
+ onOpenDrawer: (dealId: string, uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void,
docStatusMap: DocStatusMap = {},
onUpload?: (deal: ClassifiedDeal) => void,
): ColumnDef[] {
@@ -138,14 +138,14 @@ export function createDocumentTableColumns(
{
id: "retroAssessmentStatus",
accessorFn: (row) => {
- const status = row.uprn ? docStatusMap[row.uprn] : undefined;
+ const status = docStatusMap[row.dealId];
if (status?.isSurveyComplete) return 2;
if (status?.hasSurveyDocs) return 1;
return 0;
},
header: ({ column }) => ,
cell: ({ row }) => {
- const status = row.original.uprn ? docStatusMap[row.original.uprn] : undefined;
+ const status = docStatusMap[row.original.dealId];
return ;
},
enableHiding: false,
@@ -155,7 +155,7 @@ export function createDocumentTableColumns(
{
id: "installDocs",
accessorFn: (row) => {
- const status = row.uprn ? docStatusMap[row.uprn] : undefined;
+ const status = docStatusMap[row.dealId];
const s = status?.installStatus ?? "none";
if (s === "all") return 3;
if (s === "partial") return 2;
@@ -164,7 +164,7 @@ export function createDocumentTableColumns(
},
header: ({ column }) => ,
cell: ({ row }) => {
- const status = row.original.uprn ? docStatusMap[row.original.uprn] : undefined;
+ const status = docStatusMap[row.original.dealId];
return ;
},
enableHiding: false,
@@ -177,8 +177,7 @@ export function createDocumentTableColumns(
Docs
),
cell: ({ row }) => {
- const uprn = row.original.uprn ?? "";
- const status = uprn ? docStatusMap[uprn] : undefined;
+ const status = docStatusMap[row.original.dealId];
let icon: React.ReactNode;
let className: string;
@@ -201,6 +200,7 @@ export function createDocumentTableColumns(
onOpenDrawer(
+ row.original.dealId,
row.original.uprn,
row.original.landlordPropertyId,
row.original.dealname,
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
index 824a1cbf..f162c1b3 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/LiveTracker.tsx
@@ -69,6 +69,7 @@ export default function LiveTracker({
// ── Document drawer (used by PropertyTable) ──────────────────────────
const [drawerState, setDrawerState] = useState({
open: false,
+ dealId: null,
uprn: null,
landlordPropertyId: null,
dealname: null,
@@ -100,11 +101,12 @@ export default function LiveTracker({
};
const handleOpenDrawer = (
+ dealId: string,
uprn: string | null,
landlordPropertyId: string | null,
dealname: string | null,
) => {
- setDrawerState({ open: true, uprn, landlordPropertyId, dealname });
+ setDrawerState({ open: true, dealId, uprn, landlordPropertyId, dealname });
};
if (!totalDeals) {
@@ -404,15 +406,17 @@ export default function LiveTracker({
{/* ── Document drawer ────────────────────────────────────────────── */}
setDrawerState({
open: false,
+ dealId: null,
uprn: null,
landlordPropertyId: null,
dealname: null,
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx
index c40099b8..83118c02 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyDrawer.tsx
@@ -160,6 +160,7 @@ function DocumentRow({ doc, showMeasure }: { doc: PropertyDocument; showMeasure?
// -----------------------------------------------------------------------
interface PropertyDrawerProps {
open: boolean;
+ dealId?: string | null;
uprn: string | null;
landlordPropertyId: string | null;
dealname: string | null;
@@ -169,22 +170,24 @@ interface PropertyDrawerProps {
export default function PropertyDrawer({
open,
+ dealId,
uprn,
landlordPropertyId,
dealname,
docStatus,
onClose,
}: PropertyDrawerProps) {
- const canQuery = !!(uprn || landlordPropertyId);
+ const canQuery = !!(dealId || uprn || landlordPropertyId);
const {
data: fetchedDocuments = [],
isFetching,
isError,
} = useQuery({
- queryKey: ["property-documents", uprn, landlordPropertyId],
+ queryKey: ["property-documents", dealId, uprn, landlordPropertyId],
queryFn: async () => {
const params = new URLSearchParams();
- if (uprn) params.set("uprn", uprn);
+ if (dealId) params.set("dealId", dealId);
+ else if (uprn) params.set("uprn", uprn);
else if (landlordPropertyId)
params.set("landlordPropertyId", landlordPropertyId);
const res = await fetch(
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx
index 595106f7..e206587b 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx
@@ -62,7 +62,7 @@ type RemovalFilter = "all" | "pending_removal" | "removed" | "pending_re_additio
interface PropertyTableProps {
data: ClassifiedDeal[];
- onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void;
+ onOpenDrawer: (dealId: string, uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void;
onOpenDetail?: (deal: ClassifiedDeal) => void;
showDocuments?: boolean;
docStatusMap?: DocStatusMap;
@@ -128,7 +128,7 @@ export default function PropertyTable({ data, onOpenDrawer, onOpenDetail, showDo
}
if (docFilter !== "all") {
result = result.filter((d) => {
- const status = d.uprn ? docStatusMap[d.uprn] : undefined;
+ const status = docStatusMap[d.dealId];
if (docFilter === "none") return !status || !status.hasSurveyDocs;
if (docFilter === "has_docs") return !!status?.hasSurveyDocs;
if (docFilter === "incomplete") return !!status?.hasSurveyDocs && !status.isSurveyComplete;
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx
index 5d7b6934..ed18a7c1 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx
@@ -42,10 +42,10 @@ function SortableHeader({
// -----------------------------------------------------------------------
// Column factory — takes onOpenDrawer so the Documents button can trigger it
// showDocuments controls whether the Docs action column is included
-// docStatusMap provides per-UPRN document status for status indicators
+// docStatusMap provides per-deal document status for status indicators
// -----------------------------------------------------------------------
export function createPropertyTableColumns(
- onOpenDrawer: (uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void,
+ onOpenDrawer: (dealId: string, uprn: string | null, landlordPropertyId: string | null, dealname: string | null) => void,
showDocuments: boolean = false,
docStatusMap: DocStatusMap = {},
onOpenDetail?: (deal: ClassifiedDeal) => void,
@@ -291,8 +291,7 @@ export function createPropertyTableColumns(
Docs
),
cell: ({ row }) => {
- const uprn = row.original.uprn ?? "";
- const status = uprn ? docStatusMap[uprn] : undefined;
+ const status = docStatusMap[row.original.dealId];
const isComplete = status?.isSurveyComplete;
const hasDocs = status?.hasSurveyDocs;
@@ -315,7 +314,7 @@ export function createPropertyTableColumns(
return (
onOpenDrawer(row.original.uprn, row.original.landlordPropertyId, row.original.dealname)}
+ onClick={() => onOpenDrawer(row.original.dealId, row.original.uprn, row.original.landlordPropertyId, row.original.dealname)}
className={className}
>
{icon}
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index 1b659b5c..82b4a846 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -225,95 +225,122 @@ export default async function LiveReportingPage(props: {
if (state !== "none") removalStatusByDeal[row.hubspotDealId] = state;
}
- // Fetch survey document status for all properties
- const uprnList = deals
+ // Fetch document status for all deals — two-phase strategy:
+ // Phase 1: query by dealId (reliable even when UPRN is missing from hubspot_deal_data)
+ // Phase 2: UPRN fallback only for deals that returned no results in phase 1
+ const docsByDealId = new Map>();
+
+ if (dealIds.length > 0) {
+ const phase1Rows = await db
+ .select({
+ hubsotDealId: uploadedFiles.hubsotDealId,
+ fileType: uploadedFiles.fileType,
+ measureName: uploadedFiles.measureName,
+ })
+ .from(uploadedFiles)
+ .where(inArray(uploadedFiles.hubsotDealId, dealIds));
+
+ for (const row of phase1Rows) {
+ if (!row.hubsotDealId || row.fileType === null) continue;
+ if (!docsByDealId.has(row.hubsotDealId)) docsByDealId.set(row.hubsotDealId, []);
+ docsByDealId.get(row.hubsotDealId)!.push({ fileType: row.fileType, measureName: row.measureName });
+ }
+ }
+
+ // Phase 2: for deals with no docs from phase 1 that have a UPRN, try UPRN lookup
+ const dealsWithoutDocs = deals.filter((d) => !docsByDealId.has(d.dealId));
+ const fallbackUprns = dealsWithoutDocs
.map((d) => d.uprn)
.filter((u): u is string => !!u)
- .map((u) => {
- try { return BigInt(u); } catch { return null; }
- })
+ .map((u) => { try { return BigInt(u); } catch { return null; } })
.filter((u): u is bigint => u !== null);
- let docStatusMap: DocStatusMap = {};
-
- if (uprnList.length > 0) {
- const docRows = await db
+ if (fallbackUprns.length > 0) {
+ const phase2Rows = await db
.select({
uprn: uploadedFiles.uprn,
fileType: uploadedFiles.fileType,
measureName: uploadedFiles.measureName,
})
.from(uploadedFiles)
- .where(inArray(uploadedFiles.uprn, uprnList));
+ .where(inArray(uploadedFiles.uprn, fallbackUprns));
- // Group docs by UPRN
- const docsByUprn = new Map>();
- for (const row of docRows) {
+ // Map phase 2 UPRN results back to dealId
+ const uprnToDealId = new Map(
+ dealsWithoutDocs
+ .filter((d) => d.uprn)
+ .map((d) => {
+ try { return [String(BigInt(d.uprn!)), d.dealId] as [string, string]; } catch { return null; }
+ })
+ .filter((e): e is [string, string] => e !== null),
+ );
+
+ for (const row of phase2Rows) {
if (row.uprn === null || row.fileType === null) continue;
- const key = String(row.uprn);
- if (!docsByUprn.has(key)) docsByUprn.set(key, []);
- docsByUprn.get(key)!.push({ fileType: row.fileType, measureName: row.measureName });
+ const dealId = uprnToDealId.get(String(row.uprn));
+ if (!dealId) continue;
+ if (!docsByDealId.has(dealId)) docsByDealId.set(dealId, []);
+ docsByDealId.get(dealId)!.push({ fileType: row.fileType, measureName: row.measureName });
}
+ }
- // Build measures lookup from deals (uprn → approved measures, falling back to proposed)
- const measuresByUprn = new Map();
- for (const deal of deals) {
- if (deal.uprn) {
- const key = String(deal.uprn);
- const approved = approvalsByDeal[deal.dealId] ?? [];
- const measures = approved.length > 0
- ? approved
- : (deal.proposedMeasures ?? "").split(",").map((m: string) => m.trim()).filter(Boolean);
- measuresByUprn.set(key, measures);
- }
- }
+ // Build measures lookup by dealId (approved measures, falling back to proposed)
+ const measuresByDealId = new Map();
+ for (const deal of deals) {
+ const approved = approvalsByDeal[deal.dealId] ?? [];
+ const measures = approved.length > 0
+ ? approved
+ : (deal.proposedMeasures ?? "").split(",").map((m: string) => m.trim()).filter(Boolean);
+ measuresByDealId.set(deal.dealId, measures);
+ }
- for (const [uprn, docs] of docsByUprn) {
- const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType));
- const installDocs = docs.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType));
- const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType));
+ // Build docStatusMap keyed by dealId
+ const docStatusMap: DocStatusMap = {};
- const measures = measuresByUprn.get(uprn) ?? [];
+ for (const [dealId, docs] of docsByDealId) {
+ const surveyDocs = docs.filter((d) => SURVEY_ALL_DOC_TYPES.has(d.fileType));
+ const installDocs = docs.filter((d) => !SURVEY_ALL_DOC_TYPES.has(d.fileType));
+ const surveyTypeSet = new Set(surveyDocs.map((d) => d.fileType));
- // Compute per-measure document progress against the requirements matrix
- const measureProgress: MeasureDocProgress[] = measures.map((measureName) => {
- const required = getRequiredDocs(measureName);
- const docsForMeasure = installDocs.filter((d) => d.measureName === measureName);
- const uploadedTypeSet = new Set(docsForMeasure.map((d) => d.fileType));
- const uploaded = required.filter((r) => uploadedTypeSet.has(r));
- return {
- measureName,
- required,
- uploaded,
- isComplete: uploaded.length === required.length,
- uploadedCount: uploaded.length,
- requiredCount: required.length,
- };
- });
+ const measures = measuresByDealId.get(dealId) ?? [];
- let installStatus: DocStatus["installStatus"] = "none";
- if (installDocs.length > 0) {
- if (measures.length === 0) {
- installStatus = "hasDocs";
- } else {
- installStatus = measureProgress.every((m) => m.isComplete)
- ? "all"
- : measureProgress.some((m) => m.uploadedCount > 0)
- ? "partial"
- : "none";
- }
- }
-
- const status: DocStatus = {
- presentSurveyTypes: Array.from(surveyTypeSet),
- hasSurveyDocs: surveyDocs.length > 0,
- isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) => surveyTypeSet.has(t)),
- hasInstallDocs: installDocs.length > 0,
- installStatus,
- measureProgress,
+ // Compute per-measure document progress against the requirements matrix
+ const measureProgress: MeasureDocProgress[] = measures.map((measureName) => {
+ const required = getRequiredDocs(measureName);
+ const docsForMeasure = installDocs.filter((d) => d.measureName === measureName);
+ const uploadedTypeSet = new Set(docsForMeasure.map((d) => d.fileType));
+ const uploaded = required.filter((r) => uploadedTypeSet.has(r));
+ return {
+ measureName,
+ required,
+ uploaded,
+ isComplete: uploaded.length === required.length,
+ uploadedCount: uploaded.length,
+ requiredCount: required.length,
};
- docStatusMap[uprn] = status;
+ });
+
+ let installStatus: DocStatus["installStatus"] = "none";
+ if (installDocs.length > 0) {
+ if (measures.length === 0) {
+ installStatus = "hasDocs";
+ } else {
+ installStatus = measureProgress.every((m) => m.isComplete)
+ ? "all"
+ : measureProgress.some((m) => m.uploadedCount > 0)
+ ? "partial"
+ : "none";
+ }
}
+
+ docStatusMap[dealId] = {
+ presentSurveyTypes: Array.from(surveyTypeSet),
+ hasSurveyDocs: surveyDocs.length > 0,
+ isSurveyComplete: EXPECTED_RETROFIT_ASSESSMENT_DOC_TYPES.every((t) => surveyTypeSet.has(t)),
+ hasInstallDocs: installDocs.length > 0,
+ installStatus,
+ measureProgress,
+ };
}
return (
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
index c4a070b2..f9810353 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
@@ -275,10 +275,11 @@ export type DocStatus = {
measureProgress: MeasureDocProgress[]; // one entry per proposed measure
};
-export type DocStatusMap = Record; // keyed by UPRN string
+export type DocStatusMap = Record; // keyed by dealId string
export type DocumentDrawerState = {
open: boolean;
+ dealId: string | null;
uprn: string | null;
landlordPropertyId: string | null;
dealname: string | null;
From 9946b94ede711f145deff0026ab2290cb8c14483 Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Tue, 21 Apr 2026 08:51:41 +0000
Subject: [PATCH 41/43] pushing upload status to hs
---
src/app/lib/hubspot/dealSync.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/app/lib/hubspot/dealSync.ts b/src/app/lib/hubspot/dealSync.ts
index 67443ed3..563c7034 100644
--- a/src/app/lib/hubspot/dealSync.ts
+++ b/src/app/lib/hubspot/dealSync.ts
@@ -83,6 +83,7 @@ export async function syncContractorDocUploadToHubSpot(params: {
measureProgress?: MeasureUploadProgress[];
}): Promise {
let log: string;
+ let uploadStatus: string;
if (params.measureProgress && params.measureProgress.length > 0) {
log = params.measureProgress
.map((m) => {
@@ -91,8 +92,12 @@ export async function syncContractorDocUploadToHubSpot(params: {
return `${m.measureName}: Not Started (0/${m.requiredCount} docs)`;
})
.join(" | ");
+ uploadStatus = params.measureProgress.every((m) => m.isComplete)
+ ? "Upload Complete for All Measures"
+ : "Upload in progress";
} else {
log = "Documents available - uploaded by contractor";
+ uploadStatus = "Upload in progress";
}
const maxAttempts = 3;
@@ -102,6 +107,7 @@ export async function syncContractorDocUploadToHubSpot(params: {
await client.crm.deals.basicApi.update(params.hubspotDealId, {
properties: {
contractor_document_upload_log: log,
+ contractor_document_upload_status: uploadStatus,
},
});
return;
From 580398b44776593a1dc77c3f10a74c3be0f3c205 Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Tue, 21 Apr 2026 09:22:37 +0000
Subject: [PATCH 42/43] added additional columns to properties table
---
.../your-projects/live/PropertyTable.tsx | 12 +++++
.../live/PropertyTableColumns.tsx | 48 +++++++++++++++++++
.../(portfolio)/your-projects/live/page.tsx | 4 ++
.../(portfolio)/your-projects/live/types.ts | 4 ++
4 files changed, 68 insertions(+)
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx
index e206587b..d91ff6d3 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTable.tsx
@@ -52,6 +52,10 @@ const COLUMN_LABELS: Record = {
approvedPackage: "Approved Package",
actualMeasuresInstalled: "Installed Measures",
preSapScore: "Pre-SAP",
+ eiScore: "EI Score",
+ eiScorePotential: "EI Score (Potential)",
+ epcSapScore: "EPC SAP Score",
+ epcSapScorePotential: "EPC SAP (Potential)",
lodgementStatus: "Lodgement Status",
designDate: "Design Date",
fullLodgementDate: "Lodgement Date",
@@ -82,6 +86,10 @@ const CSV_FIELDS: { key: keyof ClassifiedDeal; label: string }[] = [
{ key: "approvedPackage", label: "Approved Package" },
{ key: "actualMeasuresInstalled", label: "Installed Measures" },
{ key: "preSapScore", label: "Pre-SAP" },
+ { key: "eiScore", label: "EI Score" },
+ { key: "eiScorePotential", label: "EI Score (Potential)" },
+ { key: "epcSapScore", label: "EPC SAP Score" },
+ { key: "epcSapScorePotential", label: "EPC SAP (Potential)" },
{ key: "lodgementStatus", label: "Lodgement Status" },
{ key: "designDate", label: "Design Date" },
{ key: "fullLodgementDate", label: "Lodgement Date" },
@@ -115,6 +123,10 @@ export default function PropertyTable({ data, onOpenDrawer, onOpenDetail, showDo
approvedPackage: false,
actualMeasuresInstalled: false,
preSapScore: false,
+ eiScore: false,
+ eiScorePotential: false,
+ epcSapScore: false,
+ epcSapScorePotential: false,
lodgementStatus: false,
designDate: false,
fullLodgementDate: false,
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx
index ed18a7c1..63e62dd2 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/PropertyTableColumns.tsx
@@ -240,6 +240,54 @@ export function createPropertyTableColumns(
},
+ // ── EI score ─────────────────────────────────────────────────────────
+ {
+ accessorKey: "eiScore",
+ id: "eiScore",
+ header: ({ column }) => ,
+ cell: ({ row }) => (
+
+ {row.original.eiScore ?? —}
+
+ ),
+ },
+
+ // ── EI score (potential) ──────────────────────────────────────────────
+ {
+ accessorKey: "eiScorePotential",
+ id: "eiScorePotential",
+ header: ({ column }) => ,
+ cell: ({ row }) => (
+
+ {row.original.eiScorePotential ?? —}
+
+ ),
+ },
+
+ // ── EPC SAP score ─────────────────────────────────────────────────────
+ {
+ accessorKey: "epcSapScore",
+ id: "epcSapScore",
+ header: ({ column }) => ,
+ cell: ({ row }) => (
+
+ {row.original.epcSapScore ?? —}
+
+ ),
+ },
+
+ // ── EPC SAP score (potential) ─────────────────────────────────────────
+ {
+ accessorKey: "epcSapScorePotential",
+ id: "epcSapScorePotential",
+ header: ({ column }) => ,
+ cell: ({ row }) => (
+
+ {row.original.epcSapScorePotential ?? —}
+
+ ),
+ },
+
// ── Lodgement status ─────────────────────────────────────────────────
{
accessorKey: "lodgementStatus",
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index 82b4a846..a4bdc827 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -60,6 +60,10 @@ function mapDbRowToHubspotDeal(row: DbDeal): HubspotDeal {
confirmedSurveyDate: row.confirmedSurveyDate,
surveyedDate: row.surveyedDate,
designType: row.dealType,
+ eiScore: row.eiScore,
+ eiScorePotential: row.eiScorePotential,
+ epcSapScore: row.epcSapScore,
+ epcSapScorePotential: row.epcSapScorePotential,
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
index f9810353..5e0aa063 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
@@ -46,6 +46,10 @@ export type HubspotDeal = {
confirmedSurveyDate: Date | null;
surveyedDate: Date | null;
designType: string | null;
+ eiScore: string | null;
+ eiScorePotential: string | null;
+ epcSapScore: string | null;
+ epcSapScorePotential: string | null;
createdAt: Date;
updatedAt: Date;
From 4fdbaf26ff7ca9943d986c2039d4ee7adee8eb0b Mon Sep 17 00:00:00 2001
From: Khalim Conn-Kowlessar
Date: Tue, 21 Apr 2026 13:30:01 +0000
Subject: [PATCH 43/43] dded damp and mould comments
---
.../(portfolio)/your-projects/live/DampMouldRiskPanel.tsx | 2 ++
.../portfolio/[slug]/(portfolio)/your-projects/live/page.tsx | 1 +
.../portfolio/[slug]/(portfolio)/your-projects/live/types.ts | 1 +
3 files changed, 4 insertions(+)
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
index 4b257092..ffebcd4c 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/DampMouldRiskPanel.tsx
@@ -121,6 +121,7 @@ export default function DampMouldRiskPanel({
"dealname",
"landlordPropertyId",
"dampMouldFlag",
+ "dampMouldAndRepairComments",
"coordinator",
];
@@ -128,6 +129,7 @@ export default function DampMouldRiskPanel({
dealname: "Address",
landlordPropertyId: "Property Ref",
dampMouldFlag: "Coordinator Flag",
+ dampMouldAndRepairComments: "Comments",
coordinator: "Coordinator",
};
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
index a4bdc827..5dc9759e 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/page.tsx
@@ -42,6 +42,7 @@ function mapDbRowToHubspotDeal(row: DbDeal): HubspotDeal {
pashubLink: row.pashubLink,
sharepointLink: row.sharepointLink,
dampMouldFlag: row.dampmouldGrowth,
+ dampMouldAndRepairComments: row.damnpMouldAndRepairComments,
preSapScore: row.preSap,
coordinator: row.coordinator,
ioeV1Date: row.mtpCompletionDate,
diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
index 5e0aa063..63d022b3 100644
--- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
+++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/types.ts
@@ -28,6 +28,7 @@ export type HubspotDeal = {
pashubLink: string | null;
sharepointLink: string | null;
dampMouldFlag: string | null; // coordinator-stage damp/mould flag
+ dampMouldAndRepairComments: string | null; // coordinator damp/mould comments
preSapScore: string | null; // kept as text (HubSpot returns strings)
coordinator: string | null;
ioeV1Date: Date | null;