From 4fefec7ccad2c34d04fb46909675b81c78a9842d Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Mon, 6 Jul 2026 16:54:47 +0000
Subject: [PATCH 01/15] feat(portfolio): separate properties without a UPRN
into their own section
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The portfolio landing page listed every property in one table, mixing in the
ones that never matched to an Ordnance Survey UPRN. Those unmatched properties
need a different workflow (fix the address/postcode, then re-run the address→
UPRN search), so surface them separately above the main table.
This change does the separation only:
- getUnmatchedProperties(portfolioId): portfolio properties with uprn IS NULL
(excluding soft-deleted), falling back to the user-inputted address/postcode
when the canonical fields are empty.
- UnmatchedProperties: an amber "needs attention" card listing address +
postcode per row, with a placeholder "Search UPRN" action where the advanced
ARA search will hang off. Renders nothing when every property is matched, so
healthy portfolios stay uncluttered.
- Portfolio page renders the section above the existing PropertyTable, which is
left exactly as-is.
Follow-up (the actual feature): make address/postcode editable per row and wire
the "Search UPRN" button to the advanced ARA search.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
src/app/portfolio/[slug]/(portfolio)/page.tsx | 5 ++
.../[slug]/components/UnmatchedProperties.tsx | 86 +++++++++++++++++++
src/lib/properties/unmatched.ts | 47 ++++++++++
3 files changed, 138 insertions(+)
create mode 100644 src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
create mode 100644 src/lib/properties/unmatched.ts
diff --git a/src/app/portfolio/[slug]/(portfolio)/page.tsx b/src/app/portfolio/[slug]/(portfolio)/page.tsx
index 4f9b0cde..d4514980 100644
--- a/src/app/portfolio/[slug]/(portfolio)/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/page.tsx
@@ -1,4 +1,6 @@
import PropertyTable from "../components/PropertyTable";
+import UnmatchedProperties from "../components/UnmatchedProperties";
+import { getUnmatchedProperties } from "@/lib/properties/unmatched";
export const revalidate = 60;
@@ -11,8 +13,11 @@ export default async function Page(props: {
const params = await props.params;
const portfolioId = params.slug;
+ const unmatched = await getUnmatchedProperties(portfolioId);
+
return (
<>
+
>
);
diff --git a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
new file mode 100644
index 00000000..9d6ef33e
--- /dev/null
+++ b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
@@ -0,0 +1,86 @@
+import {
+ ExclamationTriangleIcon,
+ MagnifyingGlassIcon,
+} from "@heroicons/react/24/outline";
+import type { UnmatchedProperty } from "@/lib/properties/unmatched";
+
+// The "needs attention" workspace that separates properties with no UPRN from
+// the matched portfolio table. Read-only for now; the per-row action is where
+// the advanced ARA search (edit address/postcode → search UPRN) will hang off.
+// Renders nothing when every property is matched, so a healthy portfolio stays
+// uncluttered.
+export default function UnmatchedProperties({
+ properties,
+}: {
+ properties: UnmatchedProperty[];
+}) {
+ if (properties.length === 0) return null;
+
+ const count = properties.length;
+
+ return (
+
+
+
+
+
+
+ Properties without a UPRN
+
+
+ {count}
+
+
+
+ {count === 1 ? "This property" : "These properties"} couldn't be
+ matched to an Ordnance Survey address automatically. Check the address
+ and postcode, then run an advanced search to find the right match.
+
+
+
+
+
+
+
Address
+
Postcode
+
Action
+
+
+
+
+
+ );
+}
diff --git a/src/lib/properties/unmatched.ts b/src/lib/properties/unmatched.ts
new file mode 100644
index 00000000..9f8e6334
--- /dev/null
+++ b/src/lib/properties/unmatched.ts
@@ -0,0 +1,47 @@
+import { db } from "@/app/db/db";
+import { property } from "@/app/db/schema/property";
+import { and, asc, eq, isNull } from "drizzle-orm";
+
+export interface UnmatchedProperty {
+ id: string;
+ address: string | null;
+ postcode: string | null;
+}
+
+// Properties in a portfolio that have no UPRN — i.e. were never matched to an
+// Ordnance Survey address record. We surface these separately from the main
+// property table so the customer can correct the address/postcode and re-run
+// the address→UPRN search (the "advanced ARA search", wired up in a follow-up).
+// `markedForDeletion` rows are excluded so soft-deleted properties don't show.
+export async function getUnmatchedProperties(
+ portfolioId: string,
+): Promise {
+ if (!/^\d+$/.test(portfolioId)) return [];
+ const pid = BigInt(portfolioId);
+
+ const rows = await db
+ .select({
+ id: property.id,
+ address: property.address,
+ postcode: property.postcode,
+ userInputtedAddress: property.userInputtedAddress,
+ userInputtedPostcode: property.userInputtedPostcode,
+ })
+ .from(property)
+ .where(
+ and(
+ eq(property.portfolioId, pid),
+ isNull(property.uprn),
+ eq(property.markedForDeletion, false),
+ ),
+ )
+ .orderBy(asc(property.address));
+
+ // Prefer the canonical address; fall back to whatever the user typed on
+ // import so the row is still identifiable while it awaits a match.
+ return rows.map((r) => ({
+ id: String(r.id),
+ address: r.address ?? r.userInputtedAddress ?? null,
+ postcode: r.postcode ?? r.userInputtedPostcode ?? null,
+ }));
+}
From 23fba68727869d9980cc3192175ae654adabd0b6 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Mon, 6 Jul 2026 18:41:19 +0000
Subject: [PATCH 02/15] feat(portfolio): edit address/postcode on unmatched
properties
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Each unmatched-property row now opens a small dialog to correct its address and
postcode, saved via a new endpoint. UPRN matching (the address search) is left
for a later change — this only fixes the address text so the record is right
before matching.
- PATCH /api/portfolio/[portfolioId]/properties/[propertyId]: auth + zod,
updates address/postcode scoped by (property, portfolio).
- properties/client.ts: useUpdateAddress() mutation.
- EditAddress: the edit dialog; on save the page re-fetches (router.refresh)
so the row reflects the new values.
- UnmatchedProperties: row action swapped from the placeholder to the live
Edit dialog; portfolioId threaded through from the page.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../properties/[propertyId]/route.ts | 64 ++++++++++
src/app/portfolio/[slug]/(portfolio)/page.tsx | 2 +-
.../[slug]/components/EditAddress.tsx | 120 ++++++++++++++++++
.../[slug]/components/UnmatchedProperties.tsx | 25 ++--
src/lib/properties/client.ts | 31 +++++
5 files changed, 224 insertions(+), 18 deletions(-)
create mode 100644 src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts
create mode 100644 src/app/portfolio/[slug]/components/EditAddress.tsx
create mode 100644 src/lib/properties/client.ts
diff --git a/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts b/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts
new file mode 100644
index 00000000..4bf3ecbe
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts
@@ -0,0 +1,64 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getServerSession } from "next-auth";
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+import { z } from "zod";
+import { db } from "@/app/db/db";
+import { property } from "@/app/db/schema/property";
+import { and, eq } from "drizzle-orm";
+
+// Edit an unmatched property's address / postcode. Keyed by (property,
+// portfolio) so a caller can only touch a property in a portfolio they
+// addressed. UPRN assignment (via the address search) is deliberately out of
+// scope here — this only corrects the address text.
+const patchSchema = z
+ .object({
+ address: z.string().trim().optional(),
+ postcode: z.string().trim().optional(),
+ })
+ .refine((v) => v.address !== undefined || v.postcode !== undefined, {
+ message: "Nothing to update",
+ });
+
+export async function PATCH(
+ request: NextRequest,
+ props: { params: Promise<{ portfolioId: string; propertyId: string }> },
+) {
+ const session = await getServerSession(AuthOptions);
+ if (!session?.user?.email) {
+ return NextResponse.json({ error: "Unauthorised" }, { status: 401 });
+ }
+
+ const { portfolioId, propertyId } = await props.params;
+ if (!/^\d+$/.test(portfolioId) || !/^\d+$/.test(propertyId)) {
+ return NextResponse.json({ error: "Invalid id" }, { status: 400 });
+ }
+
+ const parsed = patchSchema.safeParse(await request.json().catch(() => null));
+ if (!parsed.success) {
+ return NextResponse.json(
+ { error: parsed.error.issues[0]?.message ?? "Invalid request" },
+ { status: 400 },
+ );
+ }
+ const { address, postcode } = parsed.data;
+
+ const updated = await db
+ .update(property)
+ .set({
+ ...(address !== undefined ? { address } : {}),
+ ...(postcode !== undefined ? { postcode } : {}),
+ updatedAt: new Date(),
+ })
+ .where(
+ and(
+ eq(property.id, BigInt(propertyId)),
+ eq(property.portfolioId, BigInt(portfolioId)),
+ ),
+ )
+ .returning({ id: property.id });
+
+ if (updated.length === 0) {
+ return NextResponse.json({ error: "Property not found" }, { status: 404 });
+ }
+ return NextResponse.json({ ok: true });
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/page.tsx b/src/app/portfolio/[slug]/(portfolio)/page.tsx
index d4514980..9af81277 100644
--- a/src/app/portfolio/[slug]/(portfolio)/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/page.tsx
@@ -17,7 +17,7 @@ export default async function Page(props: {
return (
<>
-
+
>
);
diff --git a/src/app/portfolio/[slug]/components/EditAddress.tsx b/src/app/portfolio/[slug]/components/EditAddress.tsx
new file mode 100644
index 00000000..087d1252
--- /dev/null
+++ b/src/app/portfolio/[slug]/components/EditAddress.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { PencilSquareIcon } from "@heroicons/react/24/outline";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/app/shadcn_components/ui/dialog";
+import { useUpdateAddress } from "@/lib/properties/client";
+import type { UnmatchedProperty } from "@/lib/properties/unmatched";
+
+// Row action for an unmatched property: correct its address / postcode. UPRN
+// matching is a later step — this just saves the text. On success the page
+// re-fetches so the row shows the updated values.
+export default function EditAddress({
+ property,
+ portfolioId,
+}: {
+ property: UnmatchedProperty;
+ portfolioId: string;
+}) {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [address, setAddress] = useState(property.address ?? "");
+ const [postcode, setPostcode] = useState(property.postcode ?? "");
+
+ const update = useUpdateAddress(portfolioId, property.id);
+
+ function onSave() {
+ update.mutate(
+ { address: address.trim(), postcode: postcode.trim() },
+ {
+ onSuccess: () => {
+ setOpen(false);
+ router.refresh();
+ },
+ },
+ );
+ }
+
+ return (
+ <>
+ setOpen(true)}
+ className="inline-flex items-center gap-1.5 rounded-lg border border-amber-300 bg-white px-3 py-1.5 text-xs font-semibold text-amber-800 transition-colors hover:bg-amber-100"
+ >
+
+ Edit address
+
+
+
+
+
+ Edit address
+
+ Correct the address and postcode for this property.
+
+
+
+
+
+
+ {update.error && (
+
+ {update.error.message}
+
+ )}
+ setOpen(false)}
+ className="rounded-lg px-4 py-2 text-sm font-semibold text-gray-600 hover:bg-gray-100"
+ >
+ Cancel
+
+
+ {update.isPending ? "Saving…" : "Save"}
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
index 9d6ef33e..60a1dcdf 100644
--- a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
+++ b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
@@ -1,18 +1,17 @@
-import {
- ExclamationTriangleIcon,
- MagnifyingGlassIcon,
-} from "@heroicons/react/24/outline";
+import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
import type { UnmatchedProperty } from "@/lib/properties/unmatched";
+import EditAddress from "./EditAddress";
// The "needs attention" workspace that separates properties with no UPRN from
-// the matched portfolio table. Read-only for now; the per-row action is where
-// the advanced ARA search (edit address/postcode → search UPRN) will hang off.
-// Renders nothing when every property is matched, so a healthy portfolio stays
-// uncluttered.
+// the matched portfolio table. Each row lets the user correct the address /
+// postcode (UPRN matching is a later step). Renders nothing when every property
+// is matched, so a healthy portfolio stays uncluttered.
export default function UnmatchedProperties({
properties,
+ portfolioId,
}: {
properties: UnmatchedProperty[];
+ portfolioId: string;
}) {
if (properties.length === 0) return null;
@@ -67,15 +66,7 @@ export default function UnmatchedProperties({
{p.postcode ?? — }
-
-
- Search UPRN
-
+
))}
diff --git a/src/lib/properties/client.ts b/src/lib/properties/client.ts
new file mode 100644
index 00000000..44848424
--- /dev/null
+++ b/src/lib/properties/client.ts
@@ -0,0 +1,31 @@
+"use client";
+
+import { useMutation } from "@tanstack/react-query";
+
+async function parseError(res: Response, fallback: string): Promise {
+ const body = await res.json().catch(() => ({}));
+ return new Error(body?.error ?? fallback);
+}
+
+export interface UpdateAddressInput {
+ address: string;
+ postcode: string;
+}
+
+// Save an edited address / postcode onto a property.
+export function useUpdateAddress(portfolioId: string, propertyId: string) {
+ return useMutation<{ ok: true }, Error, UpdateAddressInput>({
+ mutationFn: async (input) => {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/properties/${propertyId}`,
+ {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(input),
+ },
+ );
+ if (!res.ok) throw await parseError(res, "Failed to save address.");
+ return res.json();
+ },
+ });
+}
From 369485058bfdff993ef053591a5d05c0d4651101 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 10:38:28 +0000
Subject: [PATCH 03/15] fix(building-passport): handle properties with no UPRN
Opening the building passport of an unmatched property 500'd: the whole page is
UPRN-keyed and getSpatialData did BigInt(propertyMeta.uprn) with a null uprn
("Cannot convert null to a BigInt"). Since a property with no UPRN has no
spatial data, installed measures, or EPC to show, render a clear "not matched
yet" state with a link back to the portfolio instead of crashing.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../building-passport/[propertyId]/page.tsx | 39 +++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/src/app/portfolio/[slug]/building-passport/[propertyId]/page.tsx b/src/app/portfolio/[slug]/building-passport/[propertyId]/page.tsx
index 92c12eed..ed2080be 100644
--- a/src/app/portfolio/[slug]/building-passport/[propertyId]/page.tsx
+++ b/src/app/portfolio/[slug]/building-passport/[propertyId]/page.tsx
@@ -6,8 +6,10 @@ import {
SparklesIcon,
BuildingOfficeIcon,
ShieldCheckIcon,
+ ExclamationTriangleIcon,
} from "@heroicons/react/24/outline";
import { CheckCircleIcon, XCircleIcon } from "@heroicons/react/24/solid";
+import Link from "next/link";
import {
getPropertyMeta,
getConditionReport,
@@ -67,6 +69,43 @@ export default async function BuildingPassportHome(props: {
}) {
const params = await props.params;
const propertyMeta = await getPropertyMeta(params.propertyId);
+
+ // A property with no UPRN isn't matched to an Ordnance Survey address yet, so
+ // the whole UPRN-keyed passport (spatial data, installed measures, EPC) has
+ // nothing to show — and getSpatialData(BigInt(null)) would 500. Surface a
+ // clear "not matched" state and link back to the portfolio instead.
+ if (!propertyMeta.uprn) {
+ return (
+
+
+
+
+ Not matched to a UPRN yet
+
+
+ {propertyMeta.address ? (
+ <>
+ {propertyMeta.address} {" "}
+ hasn't{" "}
+ >
+ ) : (
+ "This property hasn't "
+ )}
+ been matched to an Ordnance Survey address, so its building passport
+ isn't available yet. Match it to a UPRN from the portfolio to
+ unlock the passport.
+
+
+ Back to portfolio
+
+
+
+ );
+ }
+
const conditionReport = await getConditionReport(params.propertyId);
const spatial = await getSpatialData(propertyMeta.uprn);
const installedMeasures = await getInstalledMeasuresByUprn(Number(propertyMeta.uprn));
From f1cef2e6434acaa2e69abb992cc620a8c96f4787 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 11:05:40 +0000
Subject: [PATCH 04/15] feat(portfolio): enable the "Bulk excel import" entry
point
The Add-properties menu had "Bulk excel import" disabled behind a "Soon" badge.
The bulk-upload flow exists (/portfolio/[id]/bulk-upload), so make the item
clickable and route to it, matching the "Search by postcode" item.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../portfolio/[slug]/components/PropertyTable.tsx | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/app/portfolio/[slug]/components/PropertyTable.tsx b/src/app/portfolio/[slug]/components/PropertyTable.tsx
index 8fd7aed1..6d452e42 100644
--- a/src/app/portfolio/[slug]/components/PropertyTable.tsx
+++ b/src/app/portfolio/[slug]/components/PropertyTable.tsx
@@ -654,21 +654,20 @@ export default function PropertyTable({
+ router.push(`/portfolio/${portfolioId}/bulk-upload`)
+ }
>
-
+
-
+
Bulk excel import
Upload a spreadsheet of addresses
-
- Soon
-
From 7ac632a7aa3445920160d1b4d04e32d3ba888df1 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 11:17:48 +0000
Subject: [PATCH 05/15] feat(portfolio): move unmatched properties into a
"Needs attention" tab
Reworks the separation from a card stacked above the table into a proper
second tab, and takes unmatched properties out of the main list entirely:
- PortfolioTabs: "Properties" | "Needs attention" tab shell with a count badge
on the second tab. Both panels stay mounted (hidden with CSS) so the main
table keeps its filter/pagination state across tab switches.
- getProperties / getPropertiesCount now exclude uprn IS NULL, so unmatched
properties no longer appear (or count) in the main table until resolved.
(Both functions are only used by the main table's /api/properties route.)
- UnmatchedProperties: lives in the tab; shows an "all matched" empty state
instead of rendering nothing, and copy updated for the edit-only scope.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
src/app/portfolio/[slug]/(portfolio)/page.tsx | 12 ++-
.../[slug]/components/PortfolioTabs.tsx | 74 +++++++++++++++++++
.../[slug]/components/UnmatchedProperties.tsx | 35 ++++++---
src/app/portfolio/[slug]/utils.ts | 6 ++
4 files changed, 113 insertions(+), 14 deletions(-)
create mode 100644 src/app/portfolio/[slug]/components/PortfolioTabs.tsx
diff --git a/src/app/portfolio/[slug]/(portfolio)/page.tsx b/src/app/portfolio/[slug]/(portfolio)/page.tsx
index 9af81277..24bd0aba 100644
--- a/src/app/portfolio/[slug]/(portfolio)/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/page.tsx
@@ -1,5 +1,6 @@
import PropertyTable from "../components/PropertyTable";
import UnmatchedProperties from "../components/UnmatchedProperties";
+import PortfolioTabs from "../components/PortfolioTabs";
import { getUnmatchedProperties } from "@/lib/properties/unmatched";
export const revalidate = 60;
@@ -16,9 +17,12 @@ export default async function Page(props: {
const unmatched = await getUnmatchedProperties(portfolioId);
return (
- <>
-
-
- >
+ }
+ needsAttention={
+
+ }
+ />
);
}
diff --git a/src/app/portfolio/[slug]/components/PortfolioTabs.tsx b/src/app/portfolio/[slug]/components/PortfolioTabs.tsx
new file mode 100644
index 00000000..59aba28d
--- /dev/null
+++ b/src/app/portfolio/[slug]/components/PortfolioTabs.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { useState } from "react";
+import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
+
+// Two-tab shell for the portfolio landing view: the main properties table, and
+// a "Needs attention" tab for properties that aren't matched to a UPRN yet.
+// Both panels stay mounted (hidden with CSS) so the table keeps its filter /
+// pagination state when the user flicks between tabs.
+export default function PortfolioTabs({
+ needsAttentionCount,
+ properties,
+ needsAttention,
+}: {
+ needsAttentionCount: number;
+ properties: React.ReactNode;
+ needsAttention: React.ReactNode;
+}) {
+ const [tab, setTab] = useState<"properties" | "needs-attention">("properties");
+
+ return (
+
+
+ setTab("properties")}
+ >
+ Properties
+
+ setTab("needs-attention")}
+ >
+
+ Needs attention
+ {needsAttentionCount > 0 && (
+
+ {needsAttentionCount}
+
+ )}
+
+
+
+
{properties}
+
+ {needsAttention}
+
+
+ );
+}
+
+function TabButton({
+ active,
+ onClick,
+ children,
+}: {
+ active: boolean;
+ onClick: () => void;
+ children: React.ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
index 60a1dcdf..adaf4e15 100644
--- a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
+++ b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
@@ -1,11 +1,13 @@
-import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
+import {
+ ExclamationTriangleIcon,
+ CheckCircleIcon,
+} from "@heroicons/react/24/outline";
import type { UnmatchedProperty } from "@/lib/properties/unmatched";
import EditAddress from "./EditAddress";
-// The "needs attention" workspace that separates properties with no UPRN from
-// the matched portfolio table. Each row lets the user correct the address /
-// postcode (UPRN matching is a later step). Renders nothing when every property
-// is matched, so a healthy portfolio stays uncluttered.
+// The "Needs attention" tab: properties with no UPRN, separated out of the main
+// table until they're matched. Each row lets the user correct the address /
+// postcode (UPRN matching is a later step).
export default function UnmatchedProperties({
properties,
portfolioId,
@@ -13,14 +15,26 @@ export default function UnmatchedProperties({
properties: UnmatchedProperty[];
portfolioId: string;
}) {
- if (properties.length === 0) return null;
-
const count = properties.length;
+ if (count === 0) {
+ return (
+
+
+
+ All properties are matched
+
+
+ Every property has a UPRN — nothing needs attention.
+
+
+ );
+ }
+
return (
@@ -35,8 +49,9 @@ export default function UnmatchedProperties({
{count === 1 ? "This property" : "These properties"} couldn't be
- matched to an Ordnance Survey address automatically. Check the address
- and postcode, then run an advanced search to find the right match.
+ matched to an Ordnance Survey address automatically. Correct the
+ address and postcode so {count === 1 ? "it's" : "they're"} ready to
+ match — they stay out of the main list until resolved.
diff --git a/src/app/portfolio/[slug]/utils.ts b/src/app/portfolio/[slug]/utils.ts
index 6014cc25..0eb1b88e 100644
--- a/src/app/portfolio/[slug]/utils.ts
+++ b/src/app/portfolio/[slug]/utils.ts
@@ -739,6 +739,9 @@ export async function getPropertiesCount(
${epcJoins}
${planJoin}
WHERE p.portfolio_id = ${portfolioId}
+ -- Unmatched (no-UPRN) properties live in the "Needs attention" tab until
+ -- resolved, so they're excluded from the main table + its count.
+ AND p.uprn IS NOT NULL
${combinedWhere}
`);
@@ -831,6 +834,9 @@ export async function getProperties(
ON epc.property_id = p.id
${newApproachJoins}
WHERE p.portfolio_id = ${portfolioId}
+ -- Unmatched (no-UPRN) properties live in the "Needs attention" tab until
+ -- resolved, so they're excluded from the main table.
+ AND p.uprn IS NOT NULL
${combinedWhere}
LIMIT ${limit} OFFSET ${offset};
`);
From aab3984b5c5384d68cc6381588af43c453244123 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 11:21:44 +0000
Subject: [PATCH 06/15] copy: reword unmatched-properties help text to
AraAddressMatcher
Co-Authored-By: Claude Opus 4.8 (1M context)
---
src/app/portfolio/[slug]/components/UnmatchedProperties.tsx | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
index adaf4e15..a1f5d03d 100644
--- a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
+++ b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
@@ -49,9 +49,9 @@ export default function UnmatchedProperties({
{count === 1 ? "This property" : "These properties"} couldn't be
- matched to an Ordnance Survey address automatically. Correct the
- address and postcode so {count === 1 ? "it's" : "they're"} ready to
- match — they stay out of the main list until resolved.
+ matched by AraAddressMatcher automatically. Double check the{" "}
+ {count === 1 ? "address" : "addresses"} and Ara will try again with
+ AraAddressMatcherPlus.
From 3f8920acd00563c983db91bcf4364223c1f1c4fa Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 11:33:01 +0000
Subject: [PATCH 07/15] feat(portfolio): match unmatched properties to a
residential UPRN
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Turns the "Needs attention" tab into an actionable per-property match flow:
edit the address, confirm the postcode, search Ordnance Survey, and pick from
the RESIDENTIAL addresses in that postcode. Every candidate is a real OS
address, so picking one assigns a guaranteed valid, residential UPRN — the
property then leaves the tab and joins the main table.
- MatchAddress dialog: postcode search + residential-only candidate list
(non-residential filtered out; already-in-portfolio shown but non-selectable,
each row shows its UPRN). Replaces the edit-only EditAddress.
- PATCH /properties/[propertyId]: assigns uprn + address + postcode (user input
kept as the fallback); (portfolio_id, uprn) clash → friendly 409.
- client.ts: searchAddresses() + useAssignUprn().
Reuses the existing /properties/search endpoint, which read-through caches OS
Places results per postcode in postcode_search (30-day TTL) — repeat searches
of the same postcode hit our DB, not the OS API (surfaced as a "from cache"
hint in the dialog). No backend trigger needed.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../properties/[propertyId]/route.ts | 86 ++++---
.../[slug]/components/EditAddress.tsx | 120 ---------
.../[slug]/components/MatchAddress.tsx | 239 ++++++++++++++++++
.../[slug]/components/UnmatchedProperties.tsx | 4 +-
src/lib/properties/client.ts | 47 +++-
5 files changed, 339 insertions(+), 157 deletions(-)
delete mode 100644 src/app/portfolio/[slug]/components/EditAddress.tsx
create mode 100644 src/app/portfolio/[slug]/components/MatchAddress.tsx
diff --git a/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts b/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts
index 4bf3ecbe..16b3e28d 100644
--- a/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts
+++ b/src/app/api/portfolio/[portfolioId]/properties/[propertyId]/route.ts
@@ -6,18 +6,26 @@ import { db } from "@/app/db/db";
import { property } from "@/app/db/schema/property";
import { and, eq } from "drizzle-orm";
-// Edit an unmatched property's address / postcode. Keyed by (property,
-// portfolio) so a caller can only touch a property in a portfolio they
-// addressed. UPRN assignment (via the address search) is deliberately out of
-// scope here — this only corrects the address text.
-const patchSchema = z
- .object({
- address: z.string().trim().optional(),
- postcode: z.string().trim().optional(),
- })
- .refine((v) => v.address !== undefined || v.postcode !== undefined, {
- message: "Nothing to update",
- });
+// Assign a chosen Ordnance Survey address (from the postcode search) to an
+// existing property: writes the canonical uprn/address/postcode and keeps what
+// the user typed as the user-inputted fallback. Keyed by (property, portfolio)
+// so a caller can only touch a property in a portfolio they addressed. The
+// candidate is guaranteed residential + UPRN-bearing by the picker UI.
+const patchSchema = z.object({
+ uprn: z.string().regex(/^\d+$/, "UPRN must be numeric"),
+ address: z.string().trim().min(1, "Address is required"),
+ postcode: z.string().trim().min(1, "Postcode is required"),
+ // What the user typed in the edit fields — kept as the raw fallback, mirroring
+ // how the bulk finaliser stores user_inputted_* alongside the matched values.
+ userInputtedAddress: z.string().trim().optional(),
+ userInputtedPostcode: z.string().trim().optional(),
+});
+
+function isUniqueViolation(err: unknown): boolean {
+ const code = (err as { code?: string })?.code;
+ const message = (err as { message?: string })?.message ?? "";
+ return code === "23505" || message.includes("uq_property_portfolio_uprn");
+}
export async function PATCH(
request: NextRequest,
@@ -40,25 +48,43 @@ export async function PATCH(
{ status: 400 },
);
}
- const { address, postcode } = parsed.data;
+ const { uprn, address, postcode, userInputtedAddress, userInputtedPostcode } =
+ parsed.data;
- const updated = await db
- .update(property)
- .set({
- ...(address !== undefined ? { address } : {}),
- ...(postcode !== undefined ? { postcode } : {}),
- updatedAt: new Date(),
- })
- .where(
- and(
- eq(property.id, BigInt(propertyId)),
- eq(property.portfolioId, BigInt(portfolioId)),
- ),
- )
- .returning({ id: property.id });
+ try {
+ const updated = await db
+ .update(property)
+ .set({
+ uprn: BigInt(uprn),
+ address,
+ postcode,
+ ...(userInputtedAddress !== undefined ? { userInputtedAddress } : {}),
+ ...(userInputtedPostcode !== undefined ? { userInputtedPostcode } : {}),
+ updatedAt: new Date(),
+ })
+ .where(
+ and(
+ eq(property.id, BigInt(propertyId)),
+ eq(property.portfolioId, BigInt(portfolioId)),
+ ),
+ )
+ .returning({ id: property.id });
- if (updated.length === 0) {
- return NextResponse.json({ error: "Property not found" }, { status: 404 });
+ if (updated.length === 0) {
+ return NextResponse.json({ error: "Property not found" }, { status: 404 });
+ }
+ return NextResponse.json({ ok: true });
+ } catch (err) {
+ if (isUniqueViolation(err)) {
+ return NextResponse.json(
+ {
+ error:
+ "That UPRN is already assigned to another property in this portfolio.",
+ },
+ { status: 409 },
+ );
+ }
+ console.error("PATCH /properties/[propertyId] error:", err);
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
}
- return NextResponse.json({ ok: true });
}
diff --git a/src/app/portfolio/[slug]/components/EditAddress.tsx b/src/app/portfolio/[slug]/components/EditAddress.tsx
deleted file mode 100644
index 087d1252..00000000
--- a/src/app/portfolio/[slug]/components/EditAddress.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import { useRouter } from "next/navigation";
-import { PencilSquareIcon } from "@heroicons/react/24/outline";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogDescription,
- DialogFooter,
-} from "@/app/shadcn_components/ui/dialog";
-import { useUpdateAddress } from "@/lib/properties/client";
-import type { UnmatchedProperty } from "@/lib/properties/unmatched";
-
-// Row action for an unmatched property: correct its address / postcode. UPRN
-// matching is a later step — this just saves the text. On success the page
-// re-fetches so the row shows the updated values.
-export default function EditAddress({
- property,
- portfolioId,
-}: {
- property: UnmatchedProperty;
- portfolioId: string;
-}) {
- const router = useRouter();
- const [open, setOpen] = useState(false);
- const [address, setAddress] = useState(property.address ?? "");
- const [postcode, setPostcode] = useState(property.postcode ?? "");
-
- const update = useUpdateAddress(portfolioId, property.id);
-
- function onSave() {
- update.mutate(
- { address: address.trim(), postcode: postcode.trim() },
- {
- onSuccess: () => {
- setOpen(false);
- router.refresh();
- },
- },
- );
- }
-
- return (
- <>
- setOpen(true)}
- className="inline-flex items-center gap-1.5 rounded-lg border border-amber-300 bg-white px-3 py-1.5 text-xs font-semibold text-amber-800 transition-colors hover:bg-amber-100"
- >
-
- Edit address
-
-
-
-
-
- Edit address
-
- Correct the address and postcode for this property.
-
-
-
-
-
-
- {update.error && (
-
- {update.error.message}
-
- )}
- setOpen(false)}
- className="rounded-lg px-4 py-2 text-sm font-semibold text-gray-600 hover:bg-gray-100"
- >
- Cancel
-
-
- {update.isPending ? "Saving…" : "Save"}
-
-
-
-
- >
- );
-}
diff --git a/src/app/portfolio/[slug]/components/MatchAddress.tsx b/src/app/portfolio/[slug]/components/MatchAddress.tsx
new file mode 100644
index 00000000..b446bd00
--- /dev/null
+++ b/src/app/portfolio/[slug]/components/MatchAddress.tsx
@@ -0,0 +1,239 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import {
+ MagnifyingGlassIcon,
+ CheckCircleIcon,
+} from "@heroicons/react/24/outline";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/app/shadcn_components/ui/dialog";
+import { describeCandidate } from "@/lib/postcodeSearch/model";
+import {
+ searchAddresses,
+ useAssignUprn,
+ type SearchCandidate,
+} from "@/lib/properties/client";
+import type { UnmatchedProperty } from "@/lib/properties/unmatched";
+
+// Row action for an unmatched property: edit the address, confirm the postcode,
+// search Ordnance Survey, and pick from the RESIDENTIAL addresses in that
+// postcode. Picking assigns that candidate's UPRN — every candidate is a real
+// OS address, so this guarantees a valid, residential UPRN. On success the
+// property leaves the "Needs attention" tab (the page re-fetches).
+export default function MatchAddress({
+ property,
+ portfolioId,
+}: {
+ property: UnmatchedProperty;
+ portfolioId: string;
+}) {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [address, setAddress] = useState(property.address ?? "");
+ const [postcode, setPostcode] = useState(property.postcode ?? "");
+ // null = not searched yet; [] = searched, no residential matches.
+ const [residential, setResidential] = useState(null);
+ const [selected, setSelected] = useState(null);
+ const [source, setSource] = useState<"cache" | "live" | null>(null);
+ const [searching, setSearching] = useState(false);
+ const [searchError, setSearchError] = useState(null);
+
+ const assign = useAssignUprn(portfolioId, property.id);
+
+ async function runSearch() {
+ setSearching(true);
+ setSearchError(null);
+ setResidential(null);
+ setSelected(null);
+ try {
+ const result = await searchAddresses(portfolioId, postcode);
+ // Residential only — we never assign a non-residential UPRN.
+ setResidential(result.candidates.filter((c) => c.selectable));
+ setSource(result.source);
+ } catch (e) {
+ setSearchError(e instanceof Error ? e.message : "Search failed.");
+ } finally {
+ setSearching(false);
+ }
+ }
+
+ function onAssign() {
+ if (!selected) return;
+ assign.mutate(
+ {
+ uprn: selected.uprn,
+ address: selected.address,
+ postcode: selected.postcode,
+ userInputtedAddress: address.trim() || undefined,
+ userInputtedPostcode: postcode.trim() || undefined,
+ },
+ {
+ onSuccess: () => {
+ setOpen(false);
+ router.refresh();
+ },
+ },
+ );
+ }
+
+ return (
+ <>
+ setOpen(true)}
+ className="inline-flex items-center gap-1.5 rounded-lg border border-amber-300 bg-white px-3 py-1.5 text-xs font-semibold text-amber-800 transition-colors hover:bg-amber-100"
+ >
+
+ Find UPRN
+
+
+
+
+
+ Match to a UPRN
+
+ Confirm the postcode and pick the matching address. Only
+ residential addresses from Ordnance Survey are shown, so the UPRN
+ is always valid.
+
+
+
+
+
+
+ Address
+
+ setAddress(e.target.value)}
+ placeholder="e.g. 20 Brenchley Road"
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-amber-400 focus:outline-none focus:ring-1 focus:ring-amber-400"
+ />
+
+
+
+
+ Postcode
+
+ setPostcode(e.target.value)}
+ placeholder="e.g. BR5 2TD"
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && postcode.trim() && !searching)
+ runSearch();
+ }}
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:border-amber-400 focus:outline-none focus:ring-1 focus:ring-amber-400"
+ />
+
+
+
+ {searching ? "Searching…" : "Search"}
+
+
+
+ {searchError &&
{searchError}
}
+
+ {residential && (
+
+
+
+ Residential addresses
+
+ {source === "cache" && (
+
+ from cache
+
+ )}
+
+
+ {residential.length === 0 ? (
+
+ No residential addresses found for that postcode.
+
+ ) : (
+
+ {residential.map((c) => {
+ const { label } = describeCandidate(c);
+ const disabled = c.alreadyInPortfolio;
+ const isSelected = selected?.uprn === c.uprn;
+ return (
+
+ setSelected(c)}
+ className={`flex w-full items-start gap-2 px-3 py-2 text-left text-sm transition-colors ${
+ disabled
+ ? "cursor-not-allowed text-gray-400"
+ : isSelected
+ ? "bg-amber-50"
+ : "hover:bg-gray-50"
+ }`}
+ >
+
+
+
+ {c.address}
+
+
+ UPRN {c.uprn} · {label}
+ {c.alreadyInPortfolio
+ ? " · already in portfolio"
+ : ""}
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ )}
+
+
+
+ {assign.error && (
+
+ {assign.error.message}
+
+ )}
+ setOpen(false)}
+ className="rounded-lg px-4 py-2 text-sm font-semibold text-gray-600 hover:bg-gray-100"
+ >
+ Cancel
+
+
+ {assign.isPending ? "Assigning…" : "Assign UPRN"}
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
index a1f5d03d..024d4225 100644
--- a/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
+++ b/src/app/portfolio/[slug]/components/UnmatchedProperties.tsx
@@ -3,7 +3,7 @@ import {
CheckCircleIcon,
} from "@heroicons/react/24/outline";
import type { UnmatchedProperty } from "@/lib/properties/unmatched";
-import EditAddress from "./EditAddress";
+import MatchAddress from "./MatchAddress";
// The "Needs attention" tab: properties with no UPRN, separated out of the main
// table until they're matched. Each row lets the user correct the address /
@@ -81,7 +81,7 @@ export default function UnmatchedProperties({
{p.postcode ?? — }
-
+
))}
diff --git a/src/lib/properties/client.ts b/src/lib/properties/client.ts
index 44848424..36480528 100644
--- a/src/lib/properties/client.ts
+++ b/src/lib/properties/client.ts
@@ -7,14 +7,51 @@ async function parseError(res: Response, fallback: string): Promise {
return new Error(body?.error ?? fallback);
}
-export interface UpdateAddressInput {
+// One address candidate returned by the portfolio postcode search, plus the
+// server-computed flag for candidates already assigned in this portfolio.
+export interface SearchCandidate {
+ uprn: string;
address: string;
postcode: string;
+ classificationCode: string | null;
+ selectable: boolean; // residential (per OS classification)
+ propertyType: string | null;
+ builtForm: string | null;
+ alreadyInPortfolio: boolean;
}
-// Save an edited address / postcode onto a property.
-export function useUpdateAddress(portfolioId: string, propertyId: string) {
- return useMutation<{ ok: true }, Error, UpdateAddressInput>({
+export interface AddressSearchResult {
+ postcode: string;
+ source: "cache" | "live";
+ candidates: SearchCandidate[];
+}
+
+// Run the postcode → address-candidate search. Reuses the add-properties
+// endpoint, which read-through caches OS Places results per postcode in the
+// postcode_search table (30-day TTL) — so repeat searches of the same postcode
+// hit our DB, not the OS API. Throws with the server's { error } on failure.
+export async function searchAddresses(
+ portfolioId: string,
+ postcode: string,
+): Promise {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/properties/search?postcode=${encodeURIComponent(postcode)}`,
+ );
+ if (!res.ok) throw await parseError(res, "Address search failed.");
+ return res.json();
+}
+
+export interface AssignUprnInput {
+ uprn: string;
+ address: string;
+ postcode: string;
+ userInputtedAddress?: string;
+ userInputtedPostcode?: string;
+}
+
+// Assign a chosen candidate's UPRN + address to a property.
+export function useAssignUprn(portfolioId: string, propertyId: string) {
+ return useMutation<{ ok: true }, Error, AssignUprnInput>({
mutationFn: async (input) => {
const res = await fetch(
`/api/portfolio/${portfolioId}/properties/${propertyId}`,
@@ -24,7 +61,7 @@ export function useUpdateAddress(portfolioId: string, propertyId: string) {
body: JSON.stringify(input),
},
);
- if (!res.ok) throw await parseError(res, "Failed to save address.");
+ if (!res.ok) throw await parseError(res, "Failed to assign UPRN.");
return res.json();
},
});
From fe55a28622393da1adbf22ac7af475d9154a4253 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 11:37:10 +0000
Subject: [PATCH 08/15] fix(portfolio): truncate long match addresses; hide
Needs attention tab when empty
- MatchAddress: long OS addresses overflowed the candidate row (text column
wasn't width-constrained). Give it min-w-0 flex-1 and truncate both lines
(full address on hover) so the dialog layout holds.
- PortfolioTabs: when nothing is unmatched, render just the properties table
with no tab strip (the "Needs attention" tab only appears when count > 0).
Co-Authored-By: Claude Opus 4.8 (1M context)
---
src/app/portfolio/[slug]/components/MatchAddress.tsx | 9 ++++++---
src/app/portfolio/[slug]/components/PortfolioTabs.tsx | 3 +++
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/app/portfolio/[slug]/components/MatchAddress.tsx b/src/app/portfolio/[slug]/components/MatchAddress.tsx
index b446bd00..549420bd 100644
--- a/src/app/portfolio/[slug]/components/MatchAddress.tsx
+++ b/src/app/portfolio/[slug]/components/MatchAddress.tsx
@@ -188,11 +188,14 @@ export default function MatchAddress({
isSelected ? "text-amber-600" : "text-gray-200"
}`}
/>
-
-
+
+
{c.address}
-
+
UPRN {c.uprn} · {label}
{c.alreadyInPortfolio
? " · already in portfolio"
diff --git a/src/app/portfolio/[slug]/components/PortfolioTabs.tsx b/src/app/portfolio/[slug]/components/PortfolioTabs.tsx
index 59aba28d..887fee92 100644
--- a/src/app/portfolio/[slug]/components/PortfolioTabs.tsx
+++ b/src/app/portfolio/[slug]/components/PortfolioTabs.tsx
@@ -18,6 +18,9 @@ export default function PortfolioTabs({
}) {
const [tab, setTab] = useState<"properties" | "needs-attention">("properties");
+ // Nothing unmatched → no tab strip at all, just the properties table.
+ if (needsAttentionCount === 0) return <>{properties}>;
+
return (
From 1ac9f0fdcf581f4db9f38717473d4709c68130e2 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 11:53:26 +0000
Subject: [PATCH 09/15] fix(portfolio): open the bulk-upload dialog from "Bulk
excel import"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The BulkUploadComingSoonModal (drag/drop upload, template download, header
validation → creates the upload and routes into the map-columns onboarding
step) existed but was never wired to anything. The "Bulk excel import" menu
item routed to the uploads list instead. Point it at the dialog so the bulk
upload onboarding starts from there.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../portfolio/[slug]/components/PropertyTable.tsx | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/app/portfolio/[slug]/components/PropertyTable.tsx b/src/app/portfolio/[slug]/components/PropertyTable.tsx
index 6d452e42..119c5a32 100644
--- a/src/app/portfolio/[slug]/components/PropertyTable.tsx
+++ b/src/app/portfolio/[slug]/components/PropertyTable.tsx
@@ -17,6 +17,7 @@ import {
} from "@heroicons/react/24/outline";
import { useRouter } from "next/navigation";
import { HomeIcon } from "@heroicons/react/24/outline";
+import BulkUploadComingSoonModal from "@/app/components/portfolio/BulkUploadComingSoonModal";
import { sapToEpc } from "@/app/utils";
import { columns } from "@/app/portfolio/[slug]/components/propertyTableColumns";
import { PropertyWithRelations } from "@/app/db/schema/property";
@@ -307,6 +308,7 @@ export default function PropertyTable({
}) {
const router = useRouter();
const [sidebarOpen, setSidebarOpen] = useState(false);
+ const [bulkImportOpen, setBulkImportOpen] = useState(false);
const [committedAddress, setCommittedAddress] = useState("");
const [committedPostcode, setCommittedPostcode] = useState("");
@@ -655,9 +657,7 @@ export default function PropertyTable({
- router.push(`/portfolio/${portfolioId}/bulk-upload`)
- }
+ onSelect={() => setBulkImportOpen(true)}
>
@@ -671,6 +671,12 @@ export default function PropertyTable({
+
+ setBulkImportOpen(false)}
+ portfolioId={portfolioId}
+ />
From e0d2b9dbefc935516f250fdb925ff9ddb3ebeb81 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 15:46:58 +0000
Subject: [PATCH 10/15] feat(bulk-upload): add uprn corrections table for
pre-finalise confirmation (ADR-0057)
Foundation for the "Addresses" review tab: captures the user's confirmed
UPRN per combiner row, keyed by source_row_id (no property.id exists
pre-finalise). At dispatchFinaliser these are overlaid onto the combiner
CSV so the finaliser builds identity + property_overrides from the
confirmed UPRN in one pass.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../db/migrations/0261_uprn_corrections.sql | 15 +
src/app/db/migrations/meta/0261_snapshot.json | 11882 ++++++++++++++++
src/app/db/migrations/meta/_journal.json | 7 +
.../db/schema/bulk_upload_uprn_corrections.ts | 46 +
4 files changed, 11950 insertions(+)
create mode 100644 src/app/db/migrations/0261_uprn_corrections.sql
create mode 100644 src/app/db/migrations/meta/0261_snapshot.json
create mode 100644 src/app/db/schema/bulk_upload_uprn_corrections.ts
diff --git a/src/app/db/migrations/0261_uprn_corrections.sql b/src/app/db/migrations/0261_uprn_corrections.sql
new file mode 100644
index 00000000..b81c825d
--- /dev/null
+++ b/src/app/db/migrations/0261_uprn_corrections.sql
@@ -0,0 +1,15 @@
+CREATE TABLE "bulk_upload_uprn_corrections" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "upload_id" uuid NOT NULL,
+ "source_row_id" text NOT NULL,
+ "uprn" text,
+ "address" text,
+ "postcode" text,
+ "marked_no_uprn" boolean DEFAULT false NOT NULL,
+ "user_id" text,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
+ CONSTRAINT "bulk_upload_uprn_corrections_upload_row_unique" UNIQUE("upload_id","source_row_id")
+);
+--> statement-breakpoint
+ALTER TABLE "bulk_upload_uprn_corrections" ADD CONSTRAINT "bulk_upload_uprn_corrections_upload_id_bulk_address_uploads_id_fk" FOREIGN KEY ("upload_id") REFERENCES "public"."bulk_address_uploads"("id") ON DELETE cascade ON UPDATE no action;
\ No newline at end of file
diff --git a/src/app/db/migrations/meta/0261_snapshot.json b/src/app/db/migrations/meta/0261_snapshot.json
new file mode 100644
index 00000000..84f48943
--- /dev/null
+++ b/src/app/db/migrations/meta/0261_snapshot.json
@@ -0,0 +1,11882 @@
+{
+ "id": "d6ffb58e-4e75-4c2d-8375-e6bad4d308e0",
+ "prevId": "9e25eb5b-9f92-4266-a437-80668bafd01c",
+ "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
+ },
+ "multi_entry_summary": {
+ "name": "multi_entry_summary",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "multi_entry_ordering": {
+ "name": "multi_entry_ordering",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "verify_ack": {
+ "name": "verify_ack",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "combined_output_s3_uri": {
+ "name": "combined_output_s3_uri",
+ "type": "text",
+ "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.bulk_upload_uprn_corrections": {
+ "name": "bulk_upload_uprn_corrections",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "upload_id": {
+ "name": "upload_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_row_id": {
+ "name": "source_row_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "marked_no_uprn": {
+ "name": "marked_no_uprn",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "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": {
+ "bulk_upload_uprn_corrections_upload_id_bulk_address_uploads_id_fk": {
+ "name": "bulk_upload_uprn_corrections_upload_id_bulk_address_uploads_id_fk",
+ "tableFrom": "bulk_upload_uprn_corrections",
+ "tableTo": "bulk_address_uploads",
+ "columnsFrom": [
+ "upload_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "bulk_upload_uprn_corrections_upload_row_unique": {
+ "name": "bulk_upload_uprn_corrections_upload_row_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "upload_id",
+ "source_row_id"
+ ]
+ }
+ },
+ "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_id": {
+ "name": "project_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
+ },
+ "booking_status": {
+ "name": "booking_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
+ },
+ "batch": {
+ "name": "batch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "batch_description": {
+ "name": "batch_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_reference": {
+ "name": "block_reference",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "nonfunded_measures": {
+ "name": "nonfunded_measures",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_prn": {
+ "name": "epc_prn",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "potential_post_sap_score_dropdown": {
+ "name": "potential_post_sap_score_dropdown",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ei_score": {
+ "name": "ei_score",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ei_score__potential_": {
+ "name": "ei_score__potential_",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_sap_score": {
+ "name": "epc_sap_score",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "epc_sap_score__potential_": {
+ "name": "epc_sap_score__potential_",
+ "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
+ },
+ "survey_type": {
+ "name": "survey_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "measures_for_pibi_ordered": {
+ "name": "measures_for_pibi_ordered",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pibi_order_date": {
+ "name": "pibi_order_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pibi_completed_date": {
+ "name": "pibi_completed_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "property_halted_date": {
+ "name": "property_halted_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "property_halted_reason": {
+ "name": "property_halted_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "technical_approved_measures_for_install": {
+ "name": "technical_approved_measures_for_install",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sent_to_installer_for_pricing": {
+ "name": "sent_to_installer_for_pricing",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domna_survey_required": {
+ "name": "domna_survey_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domna_survey_type": {
+ "name": "domna_survey_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domna_survey_date": {
+ "name": "domna_survey_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date_booking_made": {
+ "name": "date_booking_made",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_contact_date": {
+ "name": "last_contact_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_outbound_call": {
+ "name": "last_outbound_call",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_outbound_email": {
+ "name": "last_outbound_email",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_submission_date": {
+ "name": "last_submission_date",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_attempts": {
+ "name": "number_of_attempts",
+ "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_projects_data": {
+ "name": "hubspot_projects_data",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "project_id": {
+ "name": "project_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "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": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "hubspot_projects_data_project_id_unique": {
+ "name": "hubspot_projects_data_project_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "project_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.hubspot_users": {
+ "name": "hubspot_users",
+ "schema": "",
+ "columns": {
+ "hubspot_owner_id": {
+ "name": "hubspot_owner_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "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.landlord_built_form_type_overrides": {
+ "name": "landlord_built_form_type_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "built_form_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_built_form_type_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_built_form_type_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_built_form_type_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_built_form_type_overrides_portfolio_description_unique": {
+ "name": "landlord_built_form_type_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_construction_age_band_overrides": {
+ "name": "landlord_construction_age_band_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "construction_age_band",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_construction_age_band_overrides_portfolio_fk": {
+ "name": "landlord_construction_age_band_overrides_portfolio_fk",
+ "tableFrom": "landlord_construction_age_band_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_construction_age_band_portfolio_description_unique": {
+ "name": "landlord_construction_age_band_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_glazing_overrides": {
+ "name": "landlord_glazing_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "glazing",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_glazing_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_glazing_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_glazing_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_glazing_overrides_portfolio_description_unique": {
+ "name": "landlord_glazing_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_main_fuel_overrides": {
+ "name": "landlord_main_fuel_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "main_fuel",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_main_fuel_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_main_fuel_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_main_fuel_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_main_fuel_overrides_portfolio_description_unique": {
+ "name": "landlord_main_fuel_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_main_heating_system_overrides": {
+ "name": "landlord_main_heating_system_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "main_heating_system",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_main_heating_system_overrides_portfolio_fk": {
+ "name": "landlord_main_heating_system_overrides_portfolio_fk",
+ "tableFrom": "landlord_main_heating_system_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_main_heating_system_portfolio_description_unique": {
+ "name": "landlord_main_heating_system_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_property_type_overrides": {
+ "name": "landlord_property_type_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "property_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_property_type_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_property_type_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_property_type_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_property_type_overrides_portfolio_description_unique": {
+ "name": "landlord_property_type_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_roof_type_overrides": {
+ "name": "landlord_roof_type_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "roof_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_roof_type_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_roof_type_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_roof_type_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_roof_type_overrides_portfolio_description_unique": {
+ "name": "landlord_roof_type_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_wall_type_overrides": {
+ "name": "landlord_wall_type_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "wall_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_wall_type_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_wall_type_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_wall_type_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_wall_type_overrides_portfolio_description_unique": {
+ "name": "landlord_wall_type_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.landlord_water_heating_overrides": {
+ "name": "landlord_water_heating_overrides",
+ "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
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "water_heating",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "override_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "landlord_water_heating_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "landlord_water_heating_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "landlord_water_heating_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "landlord_water_heating_overrides_portfolio_description_unique": {
+ "name": "landlord_water_heating_overrides_portfolio_description_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "description"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_door": {
+ "name": "magic_plan_door",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "magic_plan_room_id": {
+ "name": "magic_plan_room_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "width_mm": {
+ "name": "width_mm",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height_mm": {
+ "name": "height_mm",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_door_magic_plan_room_id_magic_plan_room_id_fk": {
+ "name": "magic_plan_door_magic_plan_room_id_magic_plan_room_id_fk",
+ "tableFrom": "magic_plan_door",
+ "tableTo": "magic_plan_room",
+ "columnsFrom": [
+ "magic_plan_room_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_door_ventilation": {
+ "name": "magic_plan_door_ventilation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "magic_plan_door_id": {
+ "name": "magic_plan_door_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "undercut_mm": {
+ "name": "undercut_mm",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_door_ventilation_magic_plan_door_id_magic_plan_door_id_fk": {
+ "name": "magic_plan_door_ventilation_magic_plan_door_id_magic_plan_door_id_fk",
+ "tableFrom": "magic_plan_door_ventilation",
+ "tableTo": "magic_plan_door",
+ "columnsFrom": [
+ "magic_plan_door_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "magic_plan_door_ventilation_magic_plan_door_id_unique": {
+ "name": "magic_plan_door_ventilation_magic_plan_door_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "magic_plan_door_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_floor": {
+ "name": "magic_plan_floor",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "magic_plan_plan_id": {
+ "name": "magic_plan_plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_floor_magic_plan_plan_id_magic_plan_plan_id_fk": {
+ "name": "magic_plan_floor_magic_plan_plan_id_magic_plan_plan_id_fk",
+ "tableFrom": "magic_plan_floor",
+ "tableTo": "magic_plan_plan",
+ "columnsFrom": [
+ "magic_plan_plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_plan": {
+ "name": "magic_plan_plan",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "address": {
+ "name": "address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "magic_plan_uid": {
+ "name": "magic_plan_uid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_file_id": {
+ "name": "uploaded_file_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_plan_uploaded_file_id_uploaded_files_id_fk": {
+ "name": "magic_plan_plan_uploaded_file_id_uploaded_files_id_fk",
+ "tableFrom": "magic_plan_plan",
+ "tableTo": "uploaded_files",
+ "columnsFrom": [
+ "uploaded_file_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "magic_plan_plan_magic_plan_uid_unique": {
+ "name": "magic_plan_plan_magic_plan_uid_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "magic_plan_uid"
+ ]
+ },
+ "magic_plan_plan_uploaded_file_id_unique": {
+ "name": "magic_plan_plan_uploaded_file_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "uploaded_file_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_room": {
+ "name": "magic_plan_room",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "magic_plan_floor_id": {
+ "name": "magic_plan_floor_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width_m": {
+ "name": "width_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "length_m": {
+ "name": "length_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "area_m2": {
+ "name": "area_m2",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_room_magic_plan_floor_id_magic_plan_floor_id_fk": {
+ "name": "magic_plan_room_magic_plan_floor_id_magic_plan_floor_id_fk",
+ "tableFrom": "magic_plan_room",
+ "tableTo": "magic_plan_floor",
+ "columnsFrom": [
+ "magic_plan_floor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_window": {
+ "name": "magic_plan_window",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "magic_plan_room_id": {
+ "name": "magic_plan_room_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "width_m": {
+ "name": "width_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height_m": {
+ "name": "height_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "area_m2": {
+ "name": "area_m2",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_window_magic_plan_room_id_magic_plan_room_id_fk": {
+ "name": "magic_plan_window_magic_plan_room_id_magic_plan_room_id_fk",
+ "tableFrom": "magic_plan_window",
+ "tableTo": "magic_plan_room",
+ "columnsFrom": [
+ "magic_plan_room_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.magic_plan_window_ventilation": {
+ "name": "magic_plan_window_ventilation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "magic_plan_window_id": {
+ "name": "magic_plan_window_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "opening_type": {
+ "name": "opening_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "num_openings": {
+ "name": "num_openings",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pct_openable": {
+ "name": "pct_openable",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trickle_vent_area_mm2": {
+ "name": "trickle_vent_area_mm2",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "num_trickle_vents": {
+ "name": "num_trickle_vents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "magic_plan_window_ventilation_magic_plan_window_id_magic_plan_window_id_fk": {
+ "name": "magic_plan_window_ventilation_magic_plan_window_id_magic_plan_window_id_fk",
+ "tableFrom": "magic_plan_window_ventilation",
+ "tableTo": "magic_plan_window",
+ "columnsFrom": [
+ "magic_plan_window_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "magic_plan_window_ventilation_magic_plan_window_id_unique": {
+ "name": "magic_plan_window_ventilation_magic_plan_window_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "magic_plan_window_id"
+ ]
+ }
+ },
+ "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": {
+ "idx_material_active": {
+ "name": "idx_material_active",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"material\".\"is_active\"",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "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.pibi_requests": {
+ "name": "pibi_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measure_name": {
+ "name": "measure_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ordered_at": {
+ "name": "ordered_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by_user_id": {
+ "name": "created_by_user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pushed_at": {
+ "name": "pushed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_pibi_requests_deal_id": {
+ "name": "idx_pibi_requests_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_pibi_requests_portfolio_id": {
+ "name": "idx_pibi_requests_portfolio_id",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pibi_requests_portfolio_id_portfolio_id_fk": {
+ "name": "pibi_requests_portfolio_id_portfolio_id_fk",
+ "tableFrom": "pibi_requests",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "pibi_requests_created_by_user_id_user_id_fk": {
+ "name": "pibi_requests_created_by_user_id_user_id_fk",
+ "tableFrom": "pibi_requests",
+ "tableTo": "user",
+ "columnsFrom": [
+ "created_by_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "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_organisation_id_unique": {
+ "name": "portfolio_organisation_portfolio_id_organisation_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "organisation_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.portfolioInvitations": {
+ "name": "portfolioInvitations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invited_by_user_id": {
+ "name": "invited_by_user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp (6) with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "portfolioInvitations_portfolio_id_portfolio_id_fk": {
+ "name": "portfolioInvitations_portfolio_id_portfolio_id_fk",
+ "tableFrom": "portfolioInvitations",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "portfolioInvitations_invited_by_user_id_user_id_fk": {
+ "name": "portfolioInvitations_invited_by_user_id_user_id_fk",
+ "tableFrom": "portfolioInvitations",
+ "tableTo": "user",
+ "columnsFrom": [
+ "invited_by_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "portfolio_invitations_portfolio_email_unique": {
+ "name": "portfolio_invitations_portfolio_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "portfolio_id",
+ "email"
+ ]
+ }
+ },
+ "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.property_overrides": {
+ "name": "property_overrides",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "building_part": {
+ "name": "building_part",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "override_component": {
+ "name": "override_component",
+ "type": "override_component",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "override_value": {
+ "name": "override_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "original_spreadsheet_description": {
+ "name": "original_spreadsheet_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "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": {
+ "property_overrides_property_id_property_id_fk": {
+ "name": "property_overrides_property_id_property_id_fk",
+ "tableFrom": "property_overrides",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "property_overrides_portfolio_id_portfolio_id_fk": {
+ "name": "property_overrides_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_overrides",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "property_overrides_property_component_part_unique": {
+ "name": "property_overrides_property_component_part_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "property_id",
+ "override_component",
+ "building_part"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_building_part": {
+ "name": "epc_building_part",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "construction_age_band": {
+ "name": "construction_age_band",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "wall_construction": {
+ "name": "wall_construction",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "wall_insulation_type": {
+ "name": "wall_insulation_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "wall_thickness_measured": {
+ "name": "wall_thickness_measured",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "party_wall_construction": {
+ "name": "party_wall_construction",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "building_part_number": {
+ "name": "building_part_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_dry_lined": {
+ "name": "wall_dry_lined",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_thickness_mm": {
+ "name": "wall_thickness_mm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_insulation_thickness": {
+ "name": "wall_insulation_thickness",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "wall_insulation_thermal_conductivity": {
+ "name": "wall_insulation_thermal_conductivity",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_heat_loss": {
+ "name": "floor_heat_loss",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_insulation_thickness": {
+ "name": "floor_insulation_thickness",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "flat_roof_insulation_thickness": {
+ "name": "flat_roof_insulation_thickness",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_type": {
+ "name": "floor_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_construction_type": {
+ "name": "floor_construction_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_insulation_type_str": {
+ "name": "floor_insulation_type_str",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_u_value_known": {
+ "name": "floor_u_value_known",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_construction": {
+ "name": "roof_construction",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_insulation_location": {
+ "name": "roof_insulation_location",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_insulation_thickness": {
+ "name": "roof_insulation_thickness",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "roof_construction_type": {
+ "name": "roof_construction_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "curtain_wall_age": {
+ "name": "curtain_wall_age",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "room_in_roof_floor_area": {
+ "name": "room_in_roof_floor_area",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "room_in_roof_construction_age_band": {
+ "name": "room_in_roof_construction_age_band",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_area": {
+ "name": "alt_wall_1_area",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_dry_lined": {
+ "name": "alt_wall_1_dry_lined",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_construction": {
+ "name": "alt_wall_1_construction",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_insulation_type": {
+ "name": "alt_wall_1_insulation_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_thickness_measured": {
+ "name": "alt_wall_1_thickness_measured",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_insulation_thickness": {
+ "name": "alt_wall_1_insulation_thickness",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_1_is_sheltered": {
+ "name": "alt_wall_1_is_sheltered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_area": {
+ "name": "alt_wall_2_area",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_dry_lined": {
+ "name": "alt_wall_2_dry_lined",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_construction": {
+ "name": "alt_wall_2_construction",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_insulation_type": {
+ "name": "alt_wall_2_insulation_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_thickness_measured": {
+ "name": "alt_wall_2_thickness_measured",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_insulation_thickness": {
+ "name": "alt_wall_2_insulation_thickness",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alt_wall_2_is_sheltered": {
+ "name": "alt_wall_2_is_sheltered",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "ix_epc_building_part_epc_property": {
+ "name": "ix_epc_building_part_epc_property",
+ "columns": [
+ {
+ "expression": "epc_property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "epc_building_part_epc_property_id_epc_property_id_fk": {
+ "name": "epc_building_part_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_building_part",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_energy_element": {
+ "name": "epc_energy_element",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "element_type": {
+ "name": "element_type",
+ "type": "energy_element_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_efficiency_rating": {
+ "name": "energy_efficiency_rating",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "environmental_efficiency_rating": {
+ "name": "environmental_efficiency_rating",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "ix_epc_energy_element_epc_property": {
+ "name": "ix_epc_energy_element_epc_property",
+ "columns": [
+ {
+ "expression": "epc_property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "epc_energy_element_epc_property_id_epc_property_id_fk": {
+ "name": "epc_energy_element_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_energy_element",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_flat_details": {
+ "name": "epc_flat_details",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "top_storey": {
+ "name": "top_storey",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "flat_location": {
+ "name": "flat_location",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "heat_loss_corridor": {
+ "name": "heat_loss_corridor",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "storey_count": {
+ "name": "storey_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "unheated_corridor_length_m": {
+ "name": "unheated_corridor_length_m",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "epc_flat_details_epc_property_id_epc_property_id_fk": {
+ "name": "epc_flat_details_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_flat_details",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "epc_flat_details_epc_property_id_unique": {
+ "name": "epc_flat_details_epc_property_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "epc_property_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_floor_dimension": {
+ "name": "epc_floor_dimension",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_building_part_id": {
+ "name": "epc_building_part_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "floor": {
+ "name": "floor",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "room_height_m": {
+ "name": "room_height_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_floor_area_m2": {
+ "name": "total_floor_area_m2",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "party_wall_length_m": {
+ "name": "party_wall_length_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "heat_loss_perimeter_m": {
+ "name": "heat_loss_perimeter_m",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "floor_insulation": {
+ "name": "floor_insulation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "floor_construction": {
+ "name": "floor_construction",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_exposed_floor": {
+ "name": "is_exposed_floor",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "is_above_partially_heated_space": {
+ "name": "is_above_partially_heated_space",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {
+ "ix_epc_floor_dimension_building_part": {
+ "name": "ix_epc_floor_dimension_building_part",
+ "columns": [
+ {
+ "expression": "epc_building_part_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "epc_floor_dimension_epc_building_part_id_epc_building_part_id_fk": {
+ "name": "epc_floor_dimension_epc_building_part_id_epc_building_part_id_fk",
+ "tableFrom": "epc_floor_dimension",
+ "tableTo": "epc_building_part",
+ "columnsFrom": [
+ "epc_building_part_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_main_heating_detail": {
+ "name": "epc_main_heating_detail",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "has_fghrs": {
+ "name": "has_fghrs",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "main_fuel_type": {
+ "name": "main_fuel_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "heat_emitter_type": {
+ "name": "heat_emitter_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "emitter_temperature": {
+ "name": "emitter_temperature",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "main_heating_control": {
+ "name": "main_heating_control",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fan_flue_present": {
+ "name": "fan_flue_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boiler_flue_type": {
+ "name": "boiler_flue_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boiler_ignition_type": {
+ "name": "boiler_ignition_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "central_heating_pump_age": {
+ "name": "central_heating_pump_age",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "central_heating_pump_age_str": {
+ "name": "central_heating_pump_age_str",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "main_heating_index_number": {
+ "name": "main_heating_index_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_main_heating_code": {
+ "name": "sap_main_heating_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "main_heating_number": {
+ "name": "main_heating_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "main_heating_category": {
+ "name": "main_heating_category",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "main_heating_fraction": {
+ "name": "main_heating_fraction",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "main_heating_data_source": {
+ "name": "main_heating_data_source",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "condensing": {
+ "name": "condensing",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "weather_compensator": {
+ "name": "weather_compensator",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "community_heating_boiler_fuel_type": {
+ "name": "community_heating_boiler_fuel_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "community_heating_chp_fraction": {
+ "name": "community_heating_chp_fraction",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "epc_main_heating_detail_epc_property_id_epc_property_id_fk": {
+ "name": "epc_main_heating_detail_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_main_heating_detail",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_photovoltaic_array": {
+ "name": "epc_photovoltaic_array",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "array_index": {
+ "name": "array_index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "peak_power": {
+ "name": "peak_power",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pitch": {
+ "name": "pitch",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "overshading": {
+ "name": "overshading",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "epc_photovoltaic_array_epc_property_id_epc_property_id_fk": {
+ "name": "epc_photovoltaic_array_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_photovoltaic_array",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_property": {
+ "name": "epc_property",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_file_id": {
+ "name": "uploaded_file_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'lodged'"
+ },
+ "uprn": {
+ "name": "uprn",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uprn_source": {
+ "name": "uprn_source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "report_reference": {
+ "name": "report_reference",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "report_type": {
+ "name": "report_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assessment_type": {
+ "name": "assessment_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sap_version": {
+ "name": "sap_version",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "schema_type": {
+ "name": "schema_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "schema_versions_original": {
+ "name": "schema_versions_original",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "calculation_software_version": {
+ "name": "calculation_software_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "address_line_1": {
+ "name": "address_line_1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "address_line_2": {
+ "name": "address_line_2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "post_town": {
+ "name": "post_town",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "postcode": {
+ "name": "postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "region_code": {
+ "name": "region_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "language_code": {
+ "name": "language_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dwelling_type": {
+ "name": "dwelling_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "property_type": {
+ "name": "property_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "built_form": {
+ "name": "built_form",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tenure": {
+ "name": "tenure",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transaction_type": {
+ "name": "transaction_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inspection_date": {
+ "name": "inspection_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "completion_date": {
+ "name": "completion_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registration_date": {
+ "name": "registration_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_floor_area_m2": {
+ "name": "total_floor_area_m2",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "measurement_type": {
+ "name": "measurement_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "solar_water_heating": {
+ "name": "solar_water_heating",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "has_hot_water_cylinder": {
+ "name": "has_hot_water_cylinder",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "has_fixed_air_conditioning": {
+ "name": "has_fixed_air_conditioning",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "has_conservatory": {
+ "name": "has_conservatory",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_heated_separate_conservatory": {
+ "name": "has_heated_separate_conservatory",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservatory_type": {
+ "name": "conservatory_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservatory_floor_area_m2": {
+ "name": "conservatory_floor_area_m2",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservatory_glazed_perimeter_m": {
+ "name": "conservatory_glazed_perimeter_m",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservatory_double_glazed": {
+ "name": "conservatory_double_glazed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservatory_thermally_separated": {
+ "name": "conservatory_thermally_separated",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conservatory_room_height_storeys": {
+ "name": "conservatory_room_height_storeys",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "door_count": {
+ "name": "door_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "wet_rooms_count": {
+ "name": "wet_rooms_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "extensions_count": {
+ "name": "extensions_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "heated_rooms_count": {
+ "name": "heated_rooms_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "open_chimneys_count": {
+ "name": "open_chimneys_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "habitable_rooms_count": {
+ "name": "habitable_rooms_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "insulated_door_count": {
+ "name": "insulated_door_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cfl_fixed_lighting_bulbs_count": {
+ "name": "cfl_fixed_lighting_bulbs_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "led_fixed_lighting_bulbs_count": {
+ "name": "led_fixed_lighting_bulbs_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "incandescent_fixed_lighting_bulbs_count": {
+ "name": "incandescent_fixed_lighting_bulbs_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "blocked_chimneys_count": {
+ "name": "blocked_chimneys_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "draughtproofed_door_count": {
+ "name": "draughtproofed_door_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_rating_average": {
+ "name": "energy_rating_average",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "low_energy_fixed_lighting_bulbs_count": {
+ "name": "low_energy_fixed_lighting_bulbs_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fixed_lighting_outlets_count": {
+ "name": "fixed_lighting_outlets_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "low_energy_fixed_lighting_outlets_count": {
+ "name": "low_energy_fixed_lighting_outlets_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number_of_storeys": {
+ "name": "number_of_storeys",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "any_unheated_rooms": {
+ "name": "any_unheated_rooms",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hydro": {
+ "name": "hydro",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "photovoltaic_array": {
+ "name": "photovoltaic_array",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "waste_water_heat_recovery": {
+ "name": "waste_water_heat_recovery",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pressure_test": {
+ "name": "pressure_test",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pressure_test_certificate_number": {
+ "name": "pressure_test_certificate_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "percent_draughtproofed": {
+ "name": "percent_draughtproofed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "insulated_door_u_value": {
+ "name": "insulated_door_u_value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "multiple_glazed_proportion": {
+ "name": "multiple_glazed_proportion",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "windows_transmission_u_value": {
+ "name": "windows_transmission_u_value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "windows_transmission_data_source": {
+ "name": "windows_transmission_data_source",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "windows_transmission_solar_transmittance": {
+ "name": "windows_transmission_solar_transmittance",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_gas_connection_available": {
+ "name": "energy_gas_connection_available",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_meter_type": {
+ "name": "energy_meter_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_pv_battery_count": {
+ "name": "energy_pv_battery_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_wind_turbines_count": {
+ "name": "energy_wind_turbines_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_gas_smart_meter_present": {
+ "name": "energy_gas_smart_meter_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_is_dwelling_export_capable": {
+ "name": "energy_is_dwelling_export_capable",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_wind_turbines_terrain_type": {
+ "name": "energy_wind_turbines_terrain_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_electricity_smart_meter_present": {
+ "name": "energy_electricity_smart_meter_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_pv_connection": {
+ "name": "energy_pv_connection",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_pv_percent_roof_area": {
+ "name": "energy_pv_percent_roof_area",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_pv_battery_capacity": {
+ "name": "energy_pv_battery_capacity",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_wind_turbine_hub_height": {
+ "name": "energy_wind_turbine_hub_height",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_wind_turbine_rotor_diameter": {
+ "name": "energy_wind_turbine_rotor_diameter",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_pv_diverter_present": {
+ "name": "energy_pv_diverter_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "heating_cylinder_size": {
+ "name": "heating_cylinder_size",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_water_heating_code": {
+ "name": "heating_water_heating_code",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_water_heating_fuel": {
+ "name": "heating_water_heating_fuel",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_immersion_heating_type": {
+ "name": "heating_immersion_heating_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cylinder_insulation_type": {
+ "name": "heating_cylinder_insulation_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cylinder_thermostat": {
+ "name": "heating_cylinder_thermostat",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_secondary_fuel_type": {
+ "name": "heating_secondary_fuel_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_secondary_heating_type": {
+ "name": "heating_secondary_heating_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cylinder_insulation_thickness_mm": {
+ "name": "heating_cylinder_insulation_thickness_mm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cylinder_volume_measured_l": {
+ "name": "heating_cylinder_volume_measured_l",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_wwhrs_index_number_1": {
+ "name": "heating_wwhrs_index_number_1",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_wwhrs_index_number_2": {
+ "name": "heating_wwhrs_index_number_2",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_shower_outlet_type": {
+ "name": "heating_shower_outlet_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_shower_wwhrs": {
+ "name": "heating_shower_wwhrs",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_type": {
+ "name": "ventilation_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_draught_lobby": {
+ "name": "ventilation_draught_lobby",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_pressure_test": {
+ "name": "ventilation_pressure_test",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_open_flues_count": {
+ "name": "ventilation_open_flues_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_closed_flues_count": {
+ "name": "ventilation_closed_flues_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_boiler_flues_count": {
+ "name": "ventilation_boiler_flues_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_other_flues_count": {
+ "name": "ventilation_other_flues_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_extract_fans_count": {
+ "name": "ventilation_extract_fans_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_passive_vents_count": {
+ "name": "ventilation_passive_vents_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_flueless_gas_fires_count": {
+ "name": "ventilation_flueless_gas_fires_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_in_pcdf_database": {
+ "name": "ventilation_in_pcdf_database",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_ventilation": {
+ "name": "mechanical_ventilation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_vent_duct_type": {
+ "name": "mechanical_vent_duct_type",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_vent_duct_placement": {
+ "name": "mechanical_vent_duct_placement",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_vent_duct_insulation": {
+ "name": "mechanical_vent_duct_insulation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_ventilation_index_number": {
+ "name": "mechanical_ventilation_index_number",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_vent_measured_installation": {
+ "name": "mechanical_vent_measured_installation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mechanical_vent_duct_insulation_level": {
+ "name": "mechanical_vent_duct_insulation_level",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "addendum_stone_walls": {
+ "name": "addendum_stone_walls",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "addendum_system_build": {
+ "name": "addendum_system_build",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "addendum_numbers": {
+ "name": "addendum_numbers",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_number_baths": {
+ "name": "heating_number_baths",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_number_baths_wwhrs": {
+ "name": "heating_number_baths_wwhrs",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_electric_shower_count": {
+ "name": "heating_electric_shower_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_mixer_shower_count": {
+ "name": "heating_mixer_shower_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_present": {
+ "name": "ventilation_present",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "ventilation_sheltered_sides": {
+ "name": "ventilation_sheltered_sides",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_has_suspended_timber_floor": {
+ "name": "ventilation_has_suspended_timber_floor",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_suspended_timber_floor_sealed": {
+ "name": "ventilation_suspended_timber_floor_sealed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_has_draught_lobby": {
+ "name": "ventilation_has_draught_lobby",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_air_permeability_ap4_m3_h_m2": {
+ "name": "ventilation_air_permeability_ap4_m3_h_m2",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_air_permeability_ap50_m3_h_m2": {
+ "name": "ventilation_air_permeability_ap50_m3_h_m2",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ventilation_mechanical_ventilation_kind": {
+ "name": "ventilation_mechanical_ventilation_kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "uq_epc_property_property_portfolio": {
+ "name": "uq_epc_property_property_portfolio",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ix_epc_property_property_source": {
+ "name": "ix_epc_property_property_source",
+ "columns": [
+ {
+ "expression": "property_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "epc_property_property_id_property_id_fk": {
+ "name": "epc_property_property_id_property_id_fk",
+ "tableFrom": "epc_property",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "epc_property_portfolio_id_portfolio_id_fk": {
+ "name": "epc_property_portfolio_id_portfolio_id_fk",
+ "tableFrom": "epc_property",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "epc_property_uploaded_file_id_uploaded_files_id_fk": {
+ "name": "epc_property_uploaded_file_id_uploaded_files_id_fk",
+ "tableFrom": "epc_property",
+ "tableTo": "uploaded_files",
+ "columnsFrom": [
+ "uploaded_file_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "epc_property_uploaded_file_id_unique": {
+ "name": "epc_property_uploaded_file_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "uploaded_file_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_property_energy_performance": {
+ "name": "epc_property_energy_performance",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "energy_rating_current": {
+ "name": "energy_rating_current",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_consumption_current": {
+ "name": "energy_consumption_current",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environmental_impact_current": {
+ "name": "environmental_impact_current",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cost_current": {
+ "name": "heating_cost_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lighting_cost_current": {
+ "name": "lighting_cost_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hot_water_cost_current": {
+ "name": "hot_water_cost_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_emissions_current": {
+ "name": "co2_emissions_current",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_emissions_current_per_floor_area": {
+ "name": "co2_emissions_current_per_floor_area",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "current_energy_efficiency_band": {
+ "name": "current_energy_efficiency_band",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_rating_potential": {
+ "name": "energy_rating_potential",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "energy_consumption_potential": {
+ "name": "energy_consumption_potential",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environmental_impact_potential": {
+ "name": "environmental_impact_potential",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cost_potential": {
+ "name": "heating_cost_potential",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lighting_cost_potential": {
+ "name": "lighting_cost_potential",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hot_water_cost_potential": {
+ "name": "hot_water_cost_potential",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "co2_emissions_potential": {
+ "name": "co2_emissions_potential",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "potential_energy_efficiency_band": {
+ "name": "potential_energy_efficiency_band",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "epc_property_energy_performance_epc_property_id_epc_property_id_fk": {
+ "name": "epc_property_energy_performance_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_property_energy_performance",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "epc_property_energy_performance_epc_property_id_unique": {
+ "name": "epc_property_energy_performance_epc_property_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "epc_property_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_renewable_heat_incentive": {
+ "name": "epc_renewable_heat_incentive",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "space_heating_kwh": {
+ "name": "space_heating_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "water_heating_kwh": {
+ "name": "water_heating_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "impact_of_loft_insulation_kwh": {
+ "name": "impact_of_loft_insulation_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "impact_of_cavity_insulation_kwh": {
+ "name": "impact_of_cavity_insulation_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "impact_of_solid_wall_insulation_kwh": {
+ "name": "impact_of_solid_wall_insulation_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "epc_renewable_heat_incentive_epc_property_id_epc_property_id_fk": {
+ "name": "epc_renewable_heat_incentive_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_renewable_heat_incentive",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "epc_renewable_heat_incentive_epc_property_id_unique": {
+ "name": "epc_renewable_heat_incentive_epc_property_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "epc_property_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.epc_window": {
+ "name": "epc_window",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "epc_property_id": {
+ "name": "epc_property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "glazing_gap": {
+ "name": "glazing_gap",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "orientation": {
+ "name": "orientation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "window_type": {
+ "name": "window_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "glazing_type": {
+ "name": "glazing_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "window_width": {
+ "name": "window_width",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "window_height": {
+ "name": "window_height",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "draught_proofed": {
+ "name": "draught_proofed",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "window_location": {
+ "name": "window_location",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "window_wall_type": {
+ "name": "window_wall_type",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permanent_shutters_present": {
+ "name": "permanent_shutters_present",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "frame_material": {
+ "name": "frame_material",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frame_factor": {
+ "name": "frame_factor",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "permanent_shutters_insulated": {
+ "name": "permanent_shutters_insulated",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transmission_u_value": {
+ "name": "transmission_u_value",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transmission_data_source": {
+ "name": "transmission_data_source",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transmission_solar_transmittance": {
+ "name": "transmission_solar_transmittance",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "epc_window_epc_property_id_epc_property_id_fk": {
+ "name": "epc_window_epc_property_id_epc_property_id_fk",
+ "tableFrom": "epc_window",
+ "tableTo": "epc_property",
+ "columnsFrom": [
+ "epc_property_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
+ },
+ "user_inputted_address": {
+ "name": "user_inputted_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_inputted_postcode": {
+ "name": "user_inputted_postcode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lexiscore": {
+ "name": "lexiscore",
+ "type": "real",
+ "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
+ },
+ "marked_for_deletion": {
+ "name": "marked_for_deletion",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 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": {}
+ },
+ "ix_property_portfolio": {
+ "name": "ix_property_portfolio",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "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_baseline_performance": {
+ "name": "property_baseline_performance",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "property_id": {
+ "name": "property_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lodged_sap_score": {
+ "name": "lodged_sap_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_epc_band": {
+ "name": "lodged_epc_band",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_co2_emissions_t_per_yr": {
+ "name": "lodged_co2_emissions_t_per_yr",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lodged_primary_energy_intensity_kwh_per_m2_yr": {
+ "name": "lodged_primary_energy_intensity_kwh_per_m2_yr",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "effective_sap_score": {
+ "name": "effective_sap_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effective_epc_band": {
+ "name": "effective_epc_band",
+ "type": "epc",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effective_co2_emissions_t_per_yr": {
+ "name": "effective_co2_emissions_t_per_yr",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "effective_primary_energy_intensity_kwh_per_m2_yr": {
+ "name": "effective_primary_energy_intensity_kwh_per_m2_yr",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rebaseline_reason": {
+ "name": "rebaseline_reason",
+ "type": "rebaseline_reason",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "space_heating_kwh": {
+ "name": "space_heating_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "water_heating_kwh": {
+ "name": "water_heating_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "fuel_rates_period": {
+ "name": "fuel_rates_period",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_kwh": {
+ "name": "heating_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "heating_cost_gbp": {
+ "name": "heating_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hot_water_kwh": {
+ "name": "hot_water_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hot_water_cost_gbp": {
+ "name": "hot_water_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lighting_kwh": {
+ "name": "lighting_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lighting_cost_gbp": {
+ "name": "lighting_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appliances_kwh": {
+ "name": "appliances_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "appliances_cost_gbp": {
+ "name": "appliances_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cooking_kwh": {
+ "name": "cooking_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cooking_cost_gbp": {
+ "name": "cooking_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pumps_fans_kwh": {
+ "name": "pumps_fans_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pumps_fans_cost_gbp": {
+ "name": "pumps_fans_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cooling_kwh": {
+ "name": "cooling_kwh",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cooling_cost_gbp": {
+ "name": "cooling_cost_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "standing_charges_gbp": {
+ "name": "standing_charges_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seg_credit_gbp": {
+ "name": "seg_credit_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_annual_bill_gbp": {
+ "name": "total_annual_bill_gbp",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "property_baseline_performance_property_id_property_id_fk": {
+ "name": "property_baseline_performance_property_id_property_id_fk",
+ "tableFrom": "property_baseline_performance",
+ "tableTo": "property",
+ "columnsFrom": [
+ "property_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "property_baseline_performance_property_id_unique": {
+ "name": "property_baseline_performance_property_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "property_id"
+ ]
+ }
+ },
+ "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": {}
+ },
+ "idx_plan_recommendations_recommendation_id": {
+ "name": "idx_plan_recommendations_recommendation_id",
+ "columns": [
+ {
+ "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
+ },
+ "plan_id": {
+ "name": "plan_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "material_id": {
+ "name": "material_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "material_quantity": {
+ "name": "material_quantity",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "material_quantity_unit": {
+ "name": "material_quantity_unit",
+ "type": "unit_quantity",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "material_depth": {
+ "name": "material_depth",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "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_plan_id": {
+ "name": "idx_recommendation_plan_id",
+ "columns": [
+ {
+ "expression": "plan_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": {}
+ },
+ "idx_recommendation_material_id": {
+ "name": "idx_recommendation_material_id",
+ "columns": [
+ {
+ "expression": "material_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": 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"
+ },
+ "recommendation_plan_id_plan_id_fk": {
+ "name": "recommendation_plan_id_plan_id_fk",
+ "tableFrom": "recommendation",
+ "tableTo": "plan",
+ "columnsFrom": [
+ "plan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "recommendation_material_id_material_id_fk": {
+ "name": "recommendation_material_id_material_id_fk",
+ "tableFrom": "recommendation",
+ "tableTo": "material",
+ "columnsFrom": [
+ "material_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "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.property_removal_requests": {
+ "name": "property_removal_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'removal'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_at": {
+ "name": "requested_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "reviewed_by": {
+ "name": "reviewed_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reviewed_at": {
+ "name": "reviewed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_batch": {
+ "name": "original_batch",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_removal_requests_deal_id": {
+ "name": "idx_removal_requests_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_removal_requests_portfolio_id": {
+ "name": "idx_removal_requests_portfolio_id",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "property_removal_requests_portfolio_id_portfolio_id_fk": {
+ "name": "property_removal_requests_portfolio_id_portfolio_id_fk",
+ "tableFrom": "property_removal_requests",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "property_removal_requests_requested_by_user_id_fk": {
+ "name": "property_removal_requests_requested_by_user_id_fk",
+ "tableFrom": "property_removal_requests",
+ "tableTo": "user",
+ "columnsFrom": [
+ "requested_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "property_removal_requests_reviewed_by_user_id_fk": {
+ "name": "property_removal_requests_reviewed_by_user_id_fk",
+ "tableFrom": "property_removal_requests",
+ "tableTo": "user",
+ "columnsFrom": [
+ "reviewed_by"
+ ],
+ "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.survey_requests": {
+ "name": "survey_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hubspot_deal_id": {
+ "name": "hubspot_deal_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "portfolio_id": {
+ "name": "portfolio_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "survey_type": {
+ "name": "survey_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "requested_by": {
+ "name": "requested_by",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requested_at": {
+ "name": "requested_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "fulfilled_at": {
+ "name": "fulfilled_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_survey_requests_deal_id": {
+ "name": "idx_survey_requests_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_survey_requests_portfolio_id": {
+ "name": "idx_survey_requests_portfolio_id",
+ "columns": [
+ {
+ "expression": "portfolio_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "survey_requests_portfolio_id_portfolio_id_fk": {
+ "name": "survey_requests_portfolio_id_portfolio_id_fk",
+ "tableFrom": "survey_requests",
+ "tableTo": "portfolio",
+ "columnsFrom": [
+ "portfolio_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "survey_requests_requested_by_user_id_fk": {
+ "name": "survey_requests_requested_by_user_id_fk",
+ "tableFrom": "survey_requests",
+ "tableTo": "user",
+ "columnsFrom": [
+ "requested_by"
+ ],
+ "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'"
+ },
+ "service": {
+ "name": "service",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "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.user_defined_deal_measures": {
+ "name": "user_defined_deal_measures",
+ "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
+ },
+ "source": {
+ "name": "source",
+ "type": "user_defined_deal_measure_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by_user_id": {
+ "name": "created_by_user_id",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "pushed_at": {
+ "name": "pushed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_in_hubspot_at": {
+ "name": "confirmed_in_hubspot_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_user_defined_deal_measures_deal_id": {
+ "name": "idx_user_defined_deal_measures_deal_id",
+ "columns": [
+ {
+ "expression": "hubspot_deal_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_user_defined_deal_measures_source": {
+ "name": "idx_user_defined_deal_measures_source",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_defined_deal_measures_created_by_user_id_user_id_fk": {
+ "name": "user_defined_deal_measures_created_by_user_id_user_id_fk",
+ "tableFrom": "user_defined_deal_measures",
+ "tableTo": "user",
+ "columnsFrom": [
+ "created_by_user_id"
+ ],
+ "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.authRateLimits": {
+ "name": "authRateLimits",
+ "schema": "",
+ "columns": {
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "window_start": {
+ "name": "window_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "authRateLimits_scope_key_pk": {
+ "name": "authRateLimits_scope_key_pk",
+ "columns": [
+ "scope",
+ "key"
+ ]
+ }
+ },
+ "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
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "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.built_form_type": {
+ "name": "built_form_type",
+ "schema": "public",
+ "values": [
+ "Detached",
+ "Semi-Detached",
+ "Mid-Terrace",
+ "End-Terrace",
+ "Enclosed Mid-Terrace",
+ "Enclosed End-Terrace",
+ "Not Recorded",
+ "Unknown"
+ ]
+ },
+ "public.construction_age_band": {
+ "name": "construction_age_band",
+ "schema": "public",
+ "values": [
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "Unknown"
+ ]
+ },
+ "public.glazing": {
+ "name": "glazing",
+ "schema": "public",
+ "values": [
+ "Single glazing",
+ "Double glazing, 2002 or later",
+ "Double glazing, pre-2002",
+ "Triple glazing, pre-2002",
+ "Triple glazing, 2002 or later",
+ "Mixed glazing",
+ "Secondary glazing",
+ "Unknown"
+ ]
+ },
+ "public.main_fuel": {
+ "name": "main_fuel",
+ "schema": "public",
+ "values": [
+ "mains gas",
+ "mains gas (community)",
+ "electricity",
+ "electricity (community)",
+ "LPG (bulk)",
+ "bottled LPG",
+ "LPG special condition",
+ "oil",
+ "house coal",
+ "smokeless coal",
+ "dual fuel (mineral and wood)",
+ "biomass (community)",
+ "wood logs",
+ "Unknown"
+ ]
+ },
+ "public.main_heating_system": {
+ "name": "main_heating_system",
+ "schema": "public",
+ "values": [
+ "Gas boiler, combi",
+ "Gas boiler, regular",
+ "Gas CPSU",
+ "Electric storage heaters, old",
+ "Electric storage heaters, slimline",
+ "Electric storage heaters, convector",
+ "Electric storage heaters, fan",
+ "Direct-acting electric",
+ "Electric room heaters",
+ "Solid fuel room heater, closed",
+ "Air source heat pump",
+ "Community heating, boilers",
+ "Community heating, CHP and boilers",
+ "Oil room heater, 2000 or later",
+ "Gas room heater, condensing fire",
+ "Gas room heater, decorative fuel-effect",
+ "Gas room heater, flush live-effect",
+ "Gas room heater, open flue 1980 or later",
+ "Gas room heater, open flue pre-1980",
+ "Electric storage heaters, high heat retention",
+ "Solid fuel room heater, open fire",
+ "Solid fuel room heater, open fire with back boiler",
+ "Solid fuel room heater, closed with boiler",
+ "Electric boiler",
+ "Electric CPSU",
+ "Electric underfloor, in concrete slab (off-peak)",
+ "Electric underfloor, integrated storage and direct-acting",
+ "Electric underfloor, in screed above insulation",
+ "Unknown"
+ ]
+ },
+ "public.override_source": {
+ "name": "override_source",
+ "schema": "public",
+ "values": [
+ "classifier",
+ "user",
+ "os_places"
+ ]
+ },
+ "public.property_type": {
+ "name": "property_type",
+ "schema": "public",
+ "values": [
+ "House",
+ "Bungalow",
+ "Flat",
+ "Maisonette",
+ "Park home",
+ "Unknown"
+ ]
+ },
+ "public.roof_type": {
+ "name": "roof_type",
+ "schema": "public",
+ "values": [
+ "Flat, insulated",
+ "Flat, insulated (assumed)",
+ "Flat, limited insulation",
+ "Flat, limited insulation (assumed)",
+ "Flat, no insulation",
+ "Flat, no insulation (assumed)",
+ "Flat, 12 mm insulation",
+ "Flat, 25 mm insulation",
+ "Flat, 50 mm insulation",
+ "Flat, 75 mm insulation",
+ "Flat, 100 mm insulation",
+ "Flat, 125 mm insulation",
+ "Flat, 150 mm insulation",
+ "Flat, 175 mm insulation",
+ "Flat, 200 mm insulation",
+ "Flat, 225 mm insulation",
+ "Flat, 250 mm insulation",
+ "Flat, 270 mm insulation",
+ "Flat, 300 mm insulation",
+ "Flat, 350 mm insulation",
+ "Flat, 400 mm insulation",
+ "Flat, 400+ mm insulation",
+ "Pitched, insulated",
+ "Pitched, insulated (assumed)",
+ "Pitched, insulated at rafters",
+ "Pitched, limited insulation",
+ "Pitched, limited insulation (assumed)",
+ "Pitched, no insulation",
+ "Pitched, no insulation (assumed)",
+ "Pitched, Unknown loft insulation",
+ "Pitched, 0 mm loft insulation",
+ "Pitched, 12 mm loft insulation",
+ "Pitched, 25 mm loft insulation",
+ "Pitched, 50 mm loft insulation",
+ "Pitched, 75 mm loft insulation",
+ "Pitched, 100 mm loft insulation",
+ "Pitched, 125 mm loft insulation",
+ "Pitched, 150 mm loft insulation",
+ "Pitched, 175 mm loft insulation",
+ "Pitched, 200 mm loft insulation",
+ "Pitched, 225 mm loft insulation",
+ "Pitched, 250 mm loft insulation",
+ "Pitched, 270 mm loft insulation",
+ "Pitched, 300 mm loft insulation",
+ "Pitched, 350 mm loft insulation",
+ "Pitched, 400 mm loft insulation",
+ "Pitched, 400+ mm loft insulation",
+ "Roof room(s), insulated",
+ "Roof room(s), insulated (assumed)",
+ "Roof room(s), limited insulation",
+ "Roof room(s), limited insulation (assumed)",
+ "Roof room(s), no insulation",
+ "Roof room(s), no insulation (assumed)",
+ "Roof room(s), ceiling insulated",
+ "Roof room(s), thatched",
+ "Roof room(s), thatched with additional insulation",
+ "Thatched",
+ "Thatched, with additional insulation",
+ "(another dwelling above)",
+ "(same dwelling above)",
+ "(other premises above)",
+ "(another premises above)",
+ "Another Premises Above",
+ "Unknown"
+ ]
+ },
+ "public.wall_type": {
+ "name": "wall_type",
+ "schema": "public",
+ "values": [
+ "Cavity wall, filled cavity",
+ "Cavity wall, as built, insulated (assumed)",
+ "Cavity wall, as built, no insulation (assumed)",
+ "Cavity wall, as built, partial insulation (assumed)",
+ "Cavity wall, with internal insulation",
+ "Cavity wall, with external insulation",
+ "Cavity wall, filled cavity and internal insulation",
+ "Cavity wall, filled cavity and external insulation",
+ "Solid brick, as built, no insulation (assumed)",
+ "Solid brick, as built, insulated (assumed)",
+ "Solid brick, as built, partial insulation (assumed)",
+ "Solid brick, with internal insulation",
+ "Solid brick, with external insulation",
+ "Timber frame, as built, no insulation (assumed)",
+ "Timber frame, as built, insulated (assumed)",
+ "Timber frame, as built, partial insulation (assumed)",
+ "Timber frame, with additional insulation",
+ "Sandstone, as built, no insulation (assumed)",
+ "Sandstone, as built, insulated (assumed)",
+ "Sandstone, as built, partial insulation (assumed)",
+ "Sandstone, with internal insulation",
+ "Sandstone, with external insulation",
+ "Granite or whin, as built, no insulation (assumed)",
+ "Granite or whin, as built, insulated (assumed)",
+ "Granite or whin, as built, partial insulation (assumed)",
+ "Granite or whin, with internal insulation",
+ "Granite or whin, with external insulation",
+ "System built, as built, no insulation (assumed)",
+ "System built, as built, insulated (assumed)",
+ "System built, as built, partial insulation (assumed)",
+ "System built, with internal insulation",
+ "System built, with external insulation",
+ "Park home wall, as built",
+ "Park home wall, with internal insulation",
+ "Park home wall, with external insulation",
+ "Cob, as built",
+ "Cob, with internal insulation",
+ "Cob, with external insulation",
+ "Curtain wall",
+ "Curtain Wall, as built, no insulation (assumed)",
+ "Curtain Wall, as built, insulated (assumed)",
+ "Curtain Wall, filled cavity",
+ "Curtain Wall, with internal insulation",
+ "Basement wall",
+ "Basement wall, as built",
+ "Unknown"
+ ]
+ },
+ "public.water_heating": {
+ "name": "water_heating",
+ "schema": "public",
+ "values": [
+ "From main system, mains gas",
+ "From main system, electricity",
+ "From main system, oil",
+ "From main system, LPG (bulk)",
+ "From main system, bottled LPG",
+ "From main system, house coal",
+ "Electric immersion, electricity",
+ "Gas boiler/circulator, mains gas",
+ "From main system, wood logs",
+ "From main system, biomass (community)",
+ "From main system, dual fuel (mineral and wood)",
+ "From main system, biodiesel (community)",
+ "Unknown"
+ ]
+ },
+ "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.override_component": {
+ "name": "override_component",
+ "schema": "public",
+ "values": [
+ "wall_type",
+ "roof_type",
+ "property_type",
+ "built_form_type",
+ "main_fuel",
+ "glazing",
+ "construction_age_band",
+ "water_heating",
+ "main_heating_system"
+ ]
+ },
+ "public.energy_element_type": {
+ "name": "energy_element_type",
+ "schema": "public",
+ "values": [
+ "roof",
+ "wall",
+ "floor",
+ "main_heating",
+ "window",
+ "lighting",
+ "hot_water",
+ "secondary_heating",
+ "main_heating_controls"
+ ]
+ },
+ "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.rebaseline_reason": {
+ "name": "rebaseline_reason",
+ "schema": "public",
+ "values": [
+ "none",
+ "pre_sap10",
+ "physical_state_changed",
+ "both"
+ ]
+ },
+ "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",
+ "hubspot_deal_id",
+ "property_id"
+ ]
+ },
+ "public.file_source": {
+ "name": "file_source",
+ "schema": "public",
+ "values": [
+ "pas hub",
+ "sharepoint",
+ "hubspot",
+ "ecmk",
+ "contractor",
+ "magic_plan",
+ "coordination_hub",
+ "audit_generator"
+ ]
+ },
+ "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",
+ "loft_hatch_photo",
+ "dmev_photos",
+ "door_undercut_photos",
+ "trickle_vent_photos",
+ "pre_installation_building_inspection",
+ "point_of_work_risk_assessment",
+ "claim_of_compliance",
+ "mcs_compliance_certificate",
+ "certificate_of_conformity",
+ "minor_works_electrical_certificate",
+ "trustmark_licence_numbers",
+ "operative_competency",
+ "ventilation_assessment_checklist",
+ "anemometer_readings",
+ "commissioning_records",
+ "part_f_ventilation_document",
+ "handover_pack",
+ "insurance_guarantee",
+ "workmanship_warranty",
+ "g98_notification",
+ "installer_qualifications",
+ "installer_feedback",
+ "contractor_other",
+ "magic_plan_json",
+ "improvement_option_evaluation",
+ "medium_term_improvement_plan",
+ "retrofit_design_doc",
+ "ventilation_audit",
+ "other"
+ ]
+ },
+ "public.user_defined_deal_measure_source": {
+ "name": "user_defined_deal_measure_source",
+ "schema": "public",
+ "values": [
+ "instructed",
+ "pibi_ordered"
+ ]
+ },
+ "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 ec3676ed..a981644a 100644
--- a/src/app/db/migrations/meta/_journal.json
+++ b/src/app/db/migrations/meta/_journal.json
@@ -1821,6 +1821,13 @@
"when": 1783345567793,
"tag": "0260_natural_enchantress",
"breakpoints": true
+ },
+ {
+ "idx": 261,
+ "version": "7",
+ "when": 1783439171770,
+ "tag": "0261_uprn_corrections",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/app/db/schema/bulk_upload_uprn_corrections.ts b/src/app/db/schema/bulk_upload_uprn_corrections.ts
new file mode 100644
index 00000000..108ee118
--- /dev/null
+++ b/src/app/db/schema/bulk_upload_uprn_corrections.ts
@@ -0,0 +1,46 @@
+import { pgTable, uuid, text, boolean, timestamp, unique } from "drizzle-orm/pg-core";
+
+import { bulkAddressUploads } from "./bulk_address_uploads";
+
+// User confirmations of a combiner row's UPRN, captured on the pre-finalise
+// "Addresses" review tab (ADR-0057). address2uprn withholds ambiguous / no-UPRN
+// matches; the user resolves each flagged row via OS Places here, keyed by the
+// combiner CSV's `source_row_id` (there is no `property.id` yet — the finaliser
+// creates identities). At dispatchFinaliser these corrections are overlaid onto
+// the combiner CSV before the finaliser reads it, so the confirmed UPRN drives
+// both the identity insert and property_overrides in one pass — no post-finalise
+// backfill (which is why the portfolio "Unmatched" tab is retired).
+//
+// A row is resolved when it has a numeric `uprn` OR `markedNoUprn = true` (the
+// user asserts no UPRN exists — an accepted terminal state; the finaliser
+// leaves it as a no-UPRN property).
+export const bulkUploadUprnCorrections = pgTable(
+ "bulk_upload_uprn_corrections",
+ {
+ id: uuid("id").defaultRandom().primaryKey(),
+ uploadId: uuid("upload_id")
+ .notNull()
+ .references(() => bulkAddressUploads.id, { onDelete: "cascade" }),
+ // The combiner CSV join key (synthetic UUID minted at start-address-matching).
+ sourceRowId: text("source_row_id").notNull(),
+ // The confirmed UPRN; null when markedNoUprn.
+ uprn: text("uprn"),
+ address: text("address"),
+ postcode: text("postcode"),
+ // The user asserts this address has no UPRN — resolved without one.
+ markedNoUprn: boolean("marked_no_uprn").notNull().default(false),
+ userId: text("user_id"),
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
+ updatedAt: timestamp("updated_at", { withTimezone: true })
+ .notNull()
+ .defaultNow()
+ .$onUpdate(() => new Date()),
+ },
+ (t) => ({
+ // One correction per combiner row — the confirm action upserts on this.
+ uploadRowUnique: unique("bulk_upload_uprn_corrections_upload_row_unique").on(
+ t.uploadId,
+ t.sourceRowId,
+ ),
+ }),
+);
From e960b76afcbab566584ddfffd06c07d8ea60f58f Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 16:06:23 +0000
Subject: [PATCH 11/15] feat(bulk-upload): read flagged combiner rows + save
UPRN corrections (ADR-0057)
Server module + two routes for the pre-finalise Addresses tab:
- getFlaggedAddresses: reads the combiner CSV from combinedOutputS3Uri,
returns rows address2uprn could not confidently match (empty UPRN or a
non-"matched" status), joined with any saved correction.
- upsertUprnCorrection: upserts a confirmed UPRN / no-UPRN per source_row_id.
- GET .../address-matches, POST .../address-corrections (thin, auth-guarded).
tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../[uploadId]/address-corrections/route.ts | 43 +++++
.../[uploadId]/address-matches/route.ts | 22 +++
src/lib/bulkUpload/addressMatches.ts | 177 ++++++++++++++++++
3 files changed, 242 insertions(+)
create mode 100644 src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-corrections/route.ts
create mode 100644 src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-matches/route.ts
create mode 100644 src/lib/bulkUpload/addressMatches.ts
diff --git a/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-corrections/route.ts b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-corrections/route.ts
new file mode 100644
index 00000000..6ae418fa
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-corrections/route.ts
@@ -0,0 +1,43 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getServerSession } from "next-auth";
+import { z } from "zod";
+
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+import { upsertUprnCorrection } from "@/lib/bulkUpload/addressMatches";
+
+// Confirm one flagged combiner row (ADR-0057): either assign a UPRN chosen from
+// OS Places, or assert the address has none. Keyed by `source_row_id` (there is
+// no property.id yet — the finaliser creates identities). Upsert semantics, so
+// re-confirming overwrites. The confirmation is overlaid onto the combiner CSV
+// at dispatchFinaliser.
+const AssignSchema = z.object({
+ sourceRowId: z.string().min(1),
+ uprn: z.string().regex(/^\d+$/, "UPRN must be numeric"),
+ address: z.string().min(1),
+ postcode: z.string().min(1),
+});
+const NoUprnSchema = z.object({
+ sourceRowId: z.string().min(1),
+ markedNoUprn: z.literal(true),
+});
+const BodySchema = z.union([NoUprnSchema, AssignSchema]);
+
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ portfolioId: string; uploadId: string }> },
+) {
+ const session = await getServerSession(AuthOptions);
+ if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+
+ const { uploadId } = await params;
+ const parsed = BodySchema.safeParse(await request.json().catch(() => null));
+ if (!parsed.success) {
+ return NextResponse.json(
+ { error: parsed.error.issues[0]?.message ?? "Invalid request" },
+ { status: 400 },
+ );
+ }
+
+ await upsertUprnCorrection(uploadId, parsed.data, session.user?.email ?? undefined);
+ return NextResponse.json({ ok: true }, { status: 200 });
+}
diff --git a/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-matches/route.ts b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-matches/route.ts
new file mode 100644
index 00000000..a585aba7
--- /dev/null
+++ b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/address-matches/route.ts
@@ -0,0 +1,22 @@
+import { NextRequest, NextResponse } from "next/server";
+import { getServerSession } from "next-auth";
+
+import { AuthOptions } from "@/app/api/auth/[...nextauth]/authOptions";
+import { getFlaggedAddresses } from "@/lib/bulkUpload/addressMatches";
+
+// Read-only: the combiner rows address2uprn could not confidently match
+// (unmatched / ambiguous_duplicate / invalid_postcode), joined with any saved
+// correction. Drives the pre-finalise "Addresses" review tab (ADR-0057); the
+// Finalise gate blocks until every flagged row is resolved.
+export async function GET(
+ _request: NextRequest,
+ { params }: { params: Promise<{ portfolioId: string; uploadId: string }> },
+) {
+ const session = await getServerSession(AuthOptions);
+ if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+
+ const { uploadId } = await params;
+ const flagged = await getFlaggedAddresses(uploadId);
+ const unresolved = flagged.filter((f) => !f.resolved).length;
+ return NextResponse.json({ flagged, unresolved }, { status: 200 });
+}
diff --git a/src/lib/bulkUpload/addressMatches.ts b/src/lib/bulkUpload/addressMatches.ts
new file mode 100644
index 00000000..198a4c0b
--- /dev/null
+++ b/src/lib/bulkUpload/addressMatches.ts
@@ -0,0 +1,177 @@
+import { eq } from "drizzle-orm";
+import * as XLSX from "xlsx";
+
+import { db } from "@/app/db/db";
+import { bulkAddressUploads } from "@/app/db/schema/bulk_address_uploads";
+import { bulkUploadUprnCorrections } from "@/app/db/schema/bulk_upload_uprn_corrections";
+import { createRetrofitDataS3Client } from "@/app/utils/s3";
+import { SOURCE_ROW_ID_COLUMN } from "./s3Keys";
+
+// Columns address2uprn writes onto each combiner row (ADR-0057). `status` is the
+// per-row outcome; a withheld/ambiguous/no-match row has an empty `uprn`.
+const UPRN_COL = "address2uprn_uprn";
+const STATUS_COL = "address2uprn_status";
+const LEXISCORE_COL = "address2uprn_lexiscore";
+
+// A combiner row is confidently matched only when address2uprn kept a UPRN AND
+// did not flag it. Everything else — unmatched, ambiguous_duplicate,
+// invalid_postcode, error, or a legacy row with no status but no UPRN — needs
+// the user to resolve it on the Addresses tab before finalise.
+function isConfidentlyMatched(row: Record): boolean {
+ const uprn = (row[UPRN_COL] ?? "").trim();
+ const status = (row[STATUS_COL] ?? "").trim();
+ if (!uprn) return false;
+ return status === "" || status === "matched";
+}
+
+export interface FlaggedAddressRow {
+ sourceRowId: string;
+ address: string;
+ postcode: string;
+ status: string;
+ lexiscore: number | null;
+ // The candidate address2uprn found but did not trust (null when none).
+ suggestedUprn: string | null;
+ // The user's confirmation (from bulk_upload_uprn_corrections), if any.
+ resolvedUprn: string | null;
+ resolvedAddress: string | null;
+ markedNoUprn: boolean;
+ // Resolved = user picked a UPRN OR asserted there is none.
+ resolved: boolean;
+}
+
+function parseS3Uri(uri: string): { bucket: string; key: string } {
+ const url = new URL(uri); // s3://bucket/key...
+ const key = url.pathname.startsWith("/") ? url.pathname.slice(1) : url.pathname;
+ if (!url.hostname || !key) throw new Error(`Malformed S3 URI: ${uri}`);
+ return { bucket: url.hostname, key };
+}
+
+async function readCombinerRows(s3Uri: string): Promise[]> {
+ const { bucket, key } = parseS3Uri(s3Uri);
+ const s3 = createRetrofitDataS3Client();
+ const result = await s3.getObject({ Bucket: bucket, Key: key }).promise();
+ const body = result.Body?.toString("utf-8");
+ if (!body) return [];
+ const wb = XLSX.read(body, { type: "string" });
+ const sheet = wb.Sheets[wb.SheetNames[0]];
+ // Every cell as a string so a numeric UPRN never arrives as a float.
+ return XLSX.utils.sheet_to_json>(sheet, { defval: "", raw: false });
+}
+
+function buildAddress(row: Record): string {
+ return ["Address 1", "Address 2", "Address 3"]
+ .map((c) => (row[c] ?? "").trim())
+ .filter(Boolean)
+ .join(", ");
+}
+
+/**
+ * The combiner rows that need user confirmation, joined with any saved
+ * correction. Reads the combiner CSV from `combinedOutputS3Uri` (ADR-0057).
+ * Returns [] until the combiner has run.
+ */
+export async function getFlaggedAddresses(
+ uploadId: string,
+): Promise {
+ const [row] = await db
+ .select({ combinedOutputS3Uri: bulkAddressUploads.combinedOutputS3Uri })
+ .from(bulkAddressUploads)
+ .where(eq(bulkAddressUploads.id, uploadId));
+ if (!row?.combinedOutputS3Uri) return [];
+
+ const [rows, corrections] = await Promise.all([
+ readCombinerRows(row.combinedOutputS3Uri),
+ db
+ .select()
+ .from(bulkUploadUprnCorrections)
+ .where(eq(bulkUploadUprnCorrections.uploadId, uploadId)),
+ ]);
+
+ const correctionByRow = new Map(corrections.map((c) => [c.sourceRowId, c]));
+
+ return rows
+ .filter((r) => !isConfidentlyMatched(r))
+ .map((r) => {
+ const sourceRowId = (r[SOURCE_ROW_ID_COLUMN] ?? "").trim();
+ const uprn = (r[UPRN_COL] ?? "").trim();
+ const rawScore = (r[LEXISCORE_COL] ?? "").trim();
+ const lexiscore = rawScore === "" ? null : Number(rawScore);
+ const c = correctionByRow.get(sourceRowId);
+ const resolvedUprn = c?.uprn ?? null;
+ const markedNoUprn = c?.markedNoUprn ?? false;
+ return {
+ sourceRowId,
+ address: buildAddress(r),
+ postcode: (r["postcode"] ?? "").trim(),
+ status: (r[STATUS_COL] ?? (uprn ? "matched" : "unmatched")).trim(),
+ lexiscore: lexiscore !== null && Number.isFinite(lexiscore) ? lexiscore : null,
+ suggestedUprn: uprn || null,
+ resolvedUprn,
+ resolvedAddress: c?.address ?? null,
+ markedNoUprn,
+ resolved: resolvedUprn !== null || markedNoUprn,
+ };
+ });
+}
+
+/** Count of flagged rows the user has not yet resolved — gates Finalise. */
+export async function countUnresolvedFlaggedAddresses(uploadId: string): Promise {
+ const flagged = await getFlaggedAddresses(uploadId);
+ return flagged.filter((f) => !f.resolved).length;
+}
+
+export type UprnCorrectionInput =
+ | {
+ sourceRowId: string;
+ uprn: string;
+ address: string;
+ postcode: string;
+ markedNoUprn?: false;
+ }
+ | { sourceRowId: string; markedNoUprn: true };
+
+/** Upsert one combiner row's confirmation (assign a UPRN, or mark no-UPRN). */
+export async function upsertUprnCorrection(
+ uploadId: string,
+ input: UprnCorrectionInput,
+ userId?: string,
+): Promise {
+ const values = input.markedNoUprn
+ ? {
+ uploadId,
+ sourceRowId: input.sourceRowId,
+ uprn: null,
+ address: null,
+ postcode: null,
+ markedNoUprn: true as const,
+ userId: userId ?? null,
+ }
+ : {
+ uploadId,
+ sourceRowId: input.sourceRowId,
+ uprn: input.uprn,
+ address: input.address,
+ postcode: input.postcode,
+ markedNoUprn: false as const,
+ userId: userId ?? null,
+ };
+
+ await db
+ .insert(bulkUploadUprnCorrections)
+ .values(values)
+ .onConflictDoUpdate({
+ target: [
+ bulkUploadUprnCorrections.uploadId,
+ bulkUploadUprnCorrections.sourceRowId,
+ ],
+ set: {
+ uprn: values.uprn,
+ address: values.address,
+ postcode: values.postcode,
+ markedNoUprn: values.markedNoUprn,
+ userId: values.userId,
+ updatedAt: new Date(),
+ },
+ });
+}
From 6743cd8baed534f1a553c09b423075c477c781e6 Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 16:09:21 +0000
Subject: [PATCH 12/15] feat(bulk-upload): gate finalise on unresolved
addresses + overlay corrections (ADR-0057)
dispatchFinaliser now:
- blocks (before the CAS claim) when any flagged combiner row is still
unresolved -> new "unresolved_addresses" outcome; finalize route returns 409.
- overlays the user's confirmed UPRNs onto the combiner CSV (buildConfirmedCombinerUri
writes a sibling .confirmed.csv) and points the finaliser at it, so a confirmed
UPRN drives identity + property_overrides in one pass. No corrections -> original
URI unchanged.
tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../bulk-uploads/[uploadId]/finalize/route.ts | 8 +++
src/lib/bulkUpload/addressMatches.ts | 69 +++++++++++++++++++
src/lib/bulkUpload/server.ts | 23 ++++++-
3 files changed, 99 insertions(+), 1 deletion(-)
diff --git a/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/finalize/route.ts b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/finalize/route.ts
index f326c65c..c5ecc423 100644
--- a/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/finalize/route.ts
+++ b/src/app/api/portfolio/[portfolioId]/bulk-uploads/[uploadId]/finalize/route.ts
@@ -35,6 +35,14 @@ export async function POST(
return NextResponse.json({ error: "Combiner not finished" }, { status: 409 });
case "missing_task":
return NextResponse.json({ error: "Upload has no task to finalise" }, { status: 409 });
+ case "unresolved_addresses":
+ return NextResponse.json(
+ {
+ error: `${result.count} address${result.count === 1 ? "" : "es"} still need a UPRN. Resolve them on the Addresses tab before finalising.`,
+ unresolvedAddresses: result.count,
+ },
+ { status: 409 },
+ );
case "wrong_state":
return NextResponse.json(
{ error: `Upload not ready to finalize (state: ${result.current})` },
diff --git a/src/lib/bulkUpload/addressMatches.ts b/src/lib/bulkUpload/addressMatches.ts
index 198a4c0b..8d3213ab 100644
--- a/src/lib/bulkUpload/addressMatches.ts
+++ b/src/lib/bulkUpload/addressMatches.ts
@@ -121,6 +121,75 @@ export async function countUnresolvedFlaggedAddresses(uploadId: string): Promise
return flagged.filter((f) => !f.resolved).length;
}
+const ADDRESS_COL = "address2uprn_address";
+const OVERLAY_STATUS_CONFIRMED = "user_confirmed";
+const OVERLAY_STATUS_NO_UPRN = "user_no_uprn";
+
+/**
+ * Overlay the user's saved corrections onto the combiner CSV and write the
+ * result to a sibling `….confirmed.csv`, returning its S3 URI (ADR-0057). The
+ * finaliser reads this so a confirmed UPRN drives both the identity insert and
+ * property_overrides in one pass. No corrections ⇒ the original URI is returned
+ * unchanged (no rewrite). A `markedNoUprn` row keeps an empty UPRN (accepted
+ * terminal state — the finaliser leaves it as a no-UPRN property).
+ */
+export async function buildConfirmedCombinerUri(
+ uploadId: string,
+ combinedOutputS3Uri: string,
+): Promise {
+ const corrections = await db
+ .select()
+ .from(bulkUploadUprnCorrections)
+ .where(eq(bulkUploadUprnCorrections.uploadId, uploadId));
+ if (corrections.length === 0) return combinedOutputS3Uri;
+ const byRow = new Map(corrections.map((c) => [c.sourceRowId, c]));
+
+ const { bucket, key } = parseS3Uri(combinedOutputS3Uri);
+ const s3 = createRetrofitDataS3Client();
+ const result = await s3.getObject({ Bucket: bucket, Key: key }).promise();
+ const body = result.Body?.toString("utf-8");
+ if (!body) return combinedOutputS3Uri;
+
+ const wb = XLSX.read(body, { type: "string" });
+ const sheet = wb.Sheets[wb.SheetNames[0]];
+ // Array-of-arrays so every column + its order is preserved exactly; we patch
+ // only the address2uprn cells of corrected rows.
+ const aoa = XLSX.utils.sheet_to_json(sheet, {
+ header: 1,
+ defval: "",
+ raw: false,
+ blankrows: false,
+ });
+ const headers = (aoa[0] ?? []).map((h) => String(h));
+ const iRow = headers.indexOf(SOURCE_ROW_ID_COLUMN);
+ const iUprn = headers.indexOf(UPRN_COL);
+ const iStatus = headers.indexOf(STATUS_COL);
+ const iAddr = headers.indexOf(ADDRESS_COL);
+ if (iRow < 0 || iUprn < 0) return combinedOutputS3Uri; // not an address2uprn CSV
+
+ for (let r = 1; r < aoa.length; r++) {
+ const line = aoa[r];
+ const srid = String(line[iRow] ?? "").trim();
+ const c = byRow.get(srid);
+ if (!c) continue;
+ if (c.markedNoUprn) {
+ line[iUprn] = "";
+ if (iStatus >= 0) line[iStatus] = OVERLAY_STATUS_NO_UPRN;
+ } else {
+ line[iUprn] = c.uprn ?? "";
+ if (iStatus >= 0) line[iStatus] = OVERLAY_STATUS_CONFIRMED;
+ if (iAddr >= 0 && c.address) line[iAddr] = c.address;
+ }
+ }
+
+ const csv = XLSX.utils.sheet_to_csv(XLSX.utils.aoa_to_sheet(aoa));
+ const confirmedKey = `${key.replace(/\.csv$/i, "")}.confirmed.csv`;
+ await s3
+ .putObject({ Bucket: bucket, Key: confirmedKey, Body: csv, ContentType: "text/csv" })
+ .promise();
+ return `s3://${bucket}/${confirmedKey}`;
+}
+
export type UprnCorrectionInput =
| {
sourceRowId: string;
diff --git a/src/lib/bulkUpload/server.ts b/src/lib/bulkUpload/server.ts
index ca303f9b..843156df 100644
--- a/src/lib/bulkUpload/server.ts
+++ b/src/lib/bulkUpload/server.ts
@@ -20,6 +20,10 @@ import { retrofitDataS3Bucket } from "@/app/utils/s3";
import { SUBTASK_SERVICE } from "./types";
import type { MultiEntrySummary } from "./multiEntry";
import { isPermutation } from "./multiEntry";
+import {
+ buildConfirmedCombinerUri,
+ countUnresolvedFlaggedAddresses,
+} from "./addressMatches";
const REMAP_ALLOWED: ReadonlySet = new Set([
"ready_for_processing",
@@ -677,6 +681,7 @@ export type DispatchFinaliserOutcome =
| { kind: "not_yet_combined" }
| { kind: "wrong_state"; current: string }
| { kind: "missing_task" }
+ | { kind: "unresolved_addresses"; count: number }
| { kind: "trigger_failed"; status: number; message: string };
// Dispatch the async bulk_upload_finaliser (ADR-0005). Replaces the old
@@ -705,6 +710,14 @@ export async function dispatchFinaliser(args: {
const upload = guarded.upload;
if (!upload.taskId) return { kind: "missing_task" };
+ // ADR-0057: every combiner row address2uprn could not confidently match must
+ // be resolved on the Addresses tab (a chosen UPRN or an explicit no-UPRN)
+ // before finalise — otherwise a withheld row would land as an unintended
+ // no-UPRN property. Gate before the CAS claim so a blocked upload stays in
+ // awaiting_review.
+ const unresolved = await countUnresolvedFlaggedAddresses(args.uploadId);
+ if (unresolved > 0) return { kind: "unresolved_addresses", count: unresolved };
+
// CAS: atomically claim the dispatch. Only the request that flips
// awaiting_review → finalising proceeds; a concurrent one updates 0 rows.
const claimed = await db
@@ -748,10 +761,18 @@ export async function dispatchFinaliser(args: {
? `s3://${retrofitDataS3Bucket()}/${classifierCsvKey(upload.portfolioId, args.uploadId)}`
: null;
+ // Overlay the user's confirmed UPRNs onto the combiner CSV and point the
+ // finaliser at the corrected copy (ADR-0057). No corrections ⇒ the original
+ // URI is returned unchanged.
+ const confirmedS3Uri = await buildConfirmedCombinerUri(
+ args.uploadId,
+ upload.combinedOutputS3Uri!,
+ );
+
const payload = {
task_id: upload.taskId,
sub_task_id: subTask.id,
- s3_uri: upload.combinedOutputS3Uri,
+ s3_uri: confirmedS3Uri,
portfolio_id: Number(upload.portfolioId),
bulk_upload_id: args.uploadId,
classifier_s3_uri: classifierS3Uri,
From ee22764e0651a239e18f5f4ab98054494134131e Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 16:10:56 +0000
Subject: [PATCH 13/15] feat(bulk-upload): client hooks for address matches +
corrections (ADR-0057)
useAddressMatches (flagged rows + unresolved count) and useSaveUprnCorrection
(assign UPRN / mark no-UPRN), matching the existing TanStack Query v4 conventions.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
src/lib/bulkUpload/client.ts | 68 ++++++++++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
diff --git a/src/lib/bulkUpload/client.ts b/src/lib/bulkUpload/client.ts
index 3f3a34e9..939d0f01 100644
--- a/src/lib/bulkUpload/client.ts
+++ b/src/lib/bulkUpload/client.ts
@@ -150,6 +150,74 @@ export function useSampleClassifications(
});
}
+// One combiner row that address2uprn could not confidently match (ADR-0057),
+// plus any saved correction. Mirrors the server FlaggedAddressRow.
+export interface FlaggedAddress {
+ sourceRowId: string;
+ address: string;
+ postcode: string;
+ status: string;
+ lexiscore: number | null;
+ suggestedUprn: string | null;
+ resolvedUprn: string | null;
+ resolvedAddress: string | null;
+ markedNoUprn: boolean;
+ resolved: boolean;
+}
+
+export interface AddressMatchesView {
+ flagged: FlaggedAddress[];
+ unresolved: number;
+}
+
+export function useAddressMatches(
+ portfolioId: string,
+ uploadId: string,
+ enabled: boolean,
+) {
+ return useQuery({
+ queryKey: [...bulkUploadKeys.progress(uploadId), "address-matches"],
+ enabled,
+ queryFn: async () => {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/address-matches`,
+ );
+ if (!res.ok) throw await parseError(res, "Failed to load address matches.");
+ const body = await res.json();
+ return {
+ flagged: (body.flagged ?? []) as FlaggedAddress[],
+ unresolved: Number(body.unresolved ?? 0),
+ };
+ },
+ });
+}
+
+export type SaveCorrectionInput =
+ | { sourceRowId: string; uprn: string; address: string; postcode: string }
+ | { sourceRowId: string; markedNoUprn: true };
+
+export function useSaveUprnCorrection(portfolioId: string, uploadId: string) {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: async (input) => {
+ const res = await fetch(
+ `/api/portfolio/${portfolioId}/bulk-uploads/${uploadId}/address-corrections`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(input),
+ },
+ );
+ if (!res.ok) throw await parseError(res, "Failed to save address.");
+ },
+ onSuccess: () => {
+ queryClient.invalidateQueries({
+ queryKey: [...bulkUploadKeys.progress(uploadId), "address-matches"],
+ });
+ },
+ });
+}
+
export function useConfirmMultiEntryOrdering(portfolioId: string, uploadId: string) {
const queryClient = useQueryClient();
return useMutation }>({
From 8b99e7029c942e9bf458132ad58c96904b5a925e Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 16:19:18 +0000
Subject: [PATCH 14/15] feat(bulk-upload): two-tab review with pre-finalise
Addresses/UPRN confirmation (ADR-0057)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
At awaiting_review, when address2uprn flagged rows, OnboardingProgress now
shows two tabs: Classification (existing verify/unknown/ordering panels) and
Addresses. The Addresses tab (AddressesPanel) lists each flagged combiner row
with the OS Places matcher (reused search → residential candidates) and a
"No UPRN" action, saving corrections via useSaveUprnCorrection. Finalise is
gated on unresolvedAddresses === 0 with a matching disabled reason; the tab
carries a badge of the unresolved count.
tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../bulk-upload/[uploadId]/AddressesPanel.tsx | 320 ++++++++++++++++++
.../[uploadId]/OnboardingProgress.tsx | 149 +++++---
2 files changed, 428 insertions(+), 41 deletions(-)
create mode 100644 src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx
new file mode 100644
index 00000000..6f99ab1e
--- /dev/null
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx
@@ -0,0 +1,320 @@
+"use client";
+
+import { useState } from "react";
+import { MagnifyingGlassIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+ DialogFooter,
+} from "@/app/shadcn_components/ui/dialog";
+import { describeCandidate } from "@/lib/postcodeSearch/model";
+import { searchAddresses, type SearchCandidate } from "@/lib/properties/client";
+import { useSaveUprnCorrection, type FlaggedAddress } from "@/lib/bulkUpload/client";
+
+// The pre-finalise "Addresses" review tab (ADR-0057). Lists the combiner rows
+// address2uprn could not confidently match; the user resolves each via Ordnance
+// Survey (residential candidates only → a guaranteed valid UPRN) or marks it as
+// having no UPRN. Confirmations are saved as corrections (keyed by
+// source_row_id) and overlaid onto the combiner CSV at finalise.
+export default function AddressesPanel({
+ flagged,
+ portfolioId,
+ uploadId,
+}: {
+ flagged: FlaggedAddress[];
+ portfolioId: string;
+ uploadId: string;
+}) {
+ return (
+
+
+
+ Addresses that need a UPRN
+
+
+ We couldn't confidently match these to Ordnance Survey. Pick the
+ right address — or mark it as having no UPRN — so onboarding creates the
+ correct property.
+
+
+
+ {flagged.length === 0 ? (
+
Nothing to resolve.
+ ) : (
+
+ {flagged.map((row) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+function statusLabel(status: string): string {
+ switch (status) {
+ case "ambiguous_duplicate":
+ return "Ambiguous match";
+ case "unmatched":
+ return "No match";
+ case "invalid_postcode":
+ return "Invalid postcode";
+ case "error":
+ return "Match error";
+ default:
+ return status;
+ }
+}
+
+function AddressRow({
+ row,
+ portfolioId,
+ uploadId,
+}: {
+ row: FlaggedAddress;
+ portfolioId: string;
+ uploadId: string;
+}) {
+ const save = useSaveUprnCorrection(portfolioId, uploadId);
+ const [open, setOpen] = useState(false);
+ const [address, setAddress] = useState(row.address ?? "");
+ const [postcode, setPostcode] = useState(row.postcode ?? "");
+ // null = not searched yet; [] = searched, no residential matches.
+ const [residential, setResidential] = useState(null);
+ const [selected, setSelected] = useState(null);
+ const [source, setSource] = useState<"cache" | "live" | null>(null);
+ const [searching, setSearching] = useState(false);
+ const [searchError, setSearchError] = useState(null);
+
+ async function runSearch() {
+ setSearching(true);
+ setSearchError(null);
+ setResidential(null);
+ setSelected(null);
+ try {
+ const result = await searchAddresses(portfolioId, postcode);
+ // Residential only — we never assign a non-residential UPRN.
+ setResidential(result.candidates.filter((c) => c.selectable));
+ setSource(result.source);
+ } catch (e) {
+ setSearchError(e instanceof Error ? e.message : "Search failed.");
+ } finally {
+ setSearching(false);
+ }
+ }
+
+ function onAssign() {
+ if (!selected) return;
+ save.mutate(
+ {
+ sourceRowId: row.sourceRowId,
+ uprn: selected.uprn,
+ address: selected.address,
+ postcode: selected.postcode,
+ },
+ { onSuccess: () => setOpen(false) },
+ );
+ }
+
+ function onNoUprn() {
+ save.mutate({ sourceRowId: row.sourceRowId, markedNoUprn: true });
+ }
+
+ return (
+
+
+
+ {row.address || "(no address)"}
+
+
+ {row.postcode || "no postcode"}
+ {row.lexiscore != null ? ` · score ${row.lexiscore.toFixed(2)}` : ""}
+ {" · "}
+ {statusLabel(row.status)}
+
+ {row.resolved && (
+
+
+ {row.markedNoUprn
+ ? "Marked: no UPRN"
+ : `UPRN ${row.resolvedUprn}${row.resolvedAddress ? ` · ${row.resolvedAddress}` : ""}`}
+
+ )}
+
+
+
+ {row.resolved ? (
+ setOpen(true)}
+ className="text-xs text-gray-500 underline underline-offset-2 hover:text-gray-800"
+ >
+ Change
+
+ ) : (
+ <>
+ setOpen(true)}
+ className="inline-flex items-center gap-1.5 rounded-lg border border-amber-300 bg-white px-3 py-1.5 text-xs font-semibold text-amber-800 transition-colors hover:bg-amber-100"
+ >
+
+ Find UPRN
+
+
+ No UPRN
+
+ >
+ )}
+
+
+
+
+
+ Match to a UPRN
+
+ Confirm the postcode and pick the matching address. Only residential
+ addresses from Ordnance Survey are shown, so the UPRN is always valid.
+
+
+
+
+
+
+ Address
+
+ setAddress(e.target.value)}
+ placeholder="e.g. 20 Brenchley Road"
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:border-amber-400 focus:outline-none focus:ring-1 focus:ring-amber-400"
+ />
+
+
+
+
+ Postcode
+
+ setPostcode(e.target.value)}
+ placeholder="e.g. BR5 2TD"
+ onKeyDown={(e) => {
+ if (e.key === "Enter" && postcode.trim() && !searching)
+ runSearch();
+ }}
+ className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm uppercase focus:border-amber-400 focus:outline-none focus:ring-1 focus:ring-amber-400"
+ />
+
+
+
+ {searching ? "Searching…" : "Search"}
+
+
+
+ {searchError &&
{searchError}
}
+
+ {residential && (
+
+
+
+ Residential addresses
+
+ {source === "cache" && (
+
+ from cache
+
+ )}
+
+
+ {residential.length === 0 ? (
+
+ No residential addresses found for that postcode.
+
+ ) : (
+
+ {residential.map((c) => {
+ const { label } = describeCandidate(c);
+ const isSelected = selected?.uprn === c.uprn;
+ return (
+
+ setSelected(c)}
+ className={`flex w-full items-start gap-2 px-3 py-2 text-left text-sm transition-colors ${
+ isSelected ? "bg-amber-50" : "hover:bg-gray-50"
+ }`}
+ >
+
+
+
+ {c.address}
+
+
+ UPRN {c.uprn} · {label}
+ {c.alreadyInPortfolio ? " · already in portfolio" : ""}
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ )}
+
+
+
+ {save.error && (
+
+ {save.error.message}
+
+ )}
+ setOpen(false)}
+ className="rounded-lg px-4 py-2 text-sm font-semibold text-gray-600 hover:bg-gray-100"
+ >
+ Cancel
+
+
+ {save.isPending ? "Assigning…" : "Assign UPRN"}
+
+
+
+
+
+ );
+}
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx
index f5dc7cd0..fe48a58b 100644
--- a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/OnboardingProgress.tsx
@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import Link from "next/link";
import { ArrowRightIcon, CheckCircleIcon } from "@heroicons/react/24/outline";
import {
+ useAddressMatches,
useBulkUploadProgress,
useConfirmMultiEntryOrdering,
useConfirmVerification,
@@ -14,6 +15,7 @@ import {
useSampleClassifications,
type SampleClassifications,
} from "@/lib/bulkUpload/client";
+import AddressesPanel from "./AddressesPanel";
import {
partLabel,
isPermutation,
@@ -82,6 +84,9 @@ export default function OnboardingProgress({
});
const combine = useRequestCombine(portfolioId, uploadId);
const finalize = useFinalize(portfolioId, uploadId);
+ const [reviewTab, setReviewTab] = useState<"classification" | "addresses">(
+ "classification",
+ );
// Read-only classifications for the multi-entry sample (issue #298). Fetched
// only once a sample exists at awaiting_review. Hook stays above the early
@@ -90,6 +95,13 @@ export default function OnboardingProgress({
progress.data?.upload.status === "awaiting_review" &&
!!progress.data.upload.multiEntrySummary?.sample;
const classifications = useSampleClassifications(portfolioId, uploadId, sampleReady);
+ // Combiner rows address2uprn could not confidently match (ADR-0057). Fetched
+ // once at awaiting_review; drives the Addresses tab + the Finalise gate.
+ const addressMatches = useAddressMatches(
+ portfolioId,
+ uploadId,
+ progress.data?.upload.status === "awaiting_review",
+ );
if (progress.isError) return null;
if (!progress.data) {
@@ -152,11 +164,17 @@ export default function OnboardingProgress({
(n, descriptions) => n + descriptions.length,
0,
);
+ // Flagged addresses (no confident UPRN) must all be resolved before finalise,
+ // else a withheld row lands as an unintended no-UPRN property (ADR-0057).
+ const flaggedCount = addressMatches.data?.flagged.length ?? 0;
+ const unresolvedAddresses = addressMatches.data?.unresolved ?? 0;
+ const showAddressTab = isAwaitingReview && flaggedCount > 0;
const canFinalize =
isAwaitingReview &&
(!needsVerify || verifyAck) &&
(!needsOrdering || orderingConfirmed) &&
- unknownTotal === 0;
+ unknownTotal === 0 &&
+ unresolvedAddresses === 0;
return (
@@ -224,47 +242,94 @@ export default function OnboardingProgress({
)}
- {needsVerify && sample && (
-
+ {/* Two-tab review at awaiting_review: classification checks | address/UPRN
+ confirmation (ADR-0057). The Addresses tab only appears when there are
+ flagged rows; otherwise the classification panels render as before. */}
+ {showAddressTab && (
+
+ setReviewTab("classification")}
+ className={`-mb-px border-b-2 px-1 pb-2 transition-colors ${
+ reviewTab === "classification"
+ ? "border-[#14163d] font-medium text-[#14163d]"
+ : "border-transparent text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ Classification
+
+ setReviewTab("addresses")}
+ className={`-mb-px flex items-center gap-1.5 border-b-2 px-1 pb-2 transition-colors ${
+ reviewTab === "addresses"
+ ? "border-[#14163d] font-medium text-[#14163d]"
+ : "border-transparent text-gray-500 hover:text-gray-700"
+ }`}
+ >
+ Addresses
+ {unresolvedAddresses > 0 && (
+
+ {unresolvedAddresses}
+
+ )}
+
+
)}
- {isAwaitingReview && unknownTotal > 0 && (
-
- )}
-
- {needsOrdering && orderingSamples.length > 0 && (
-
- {orderingSamples.map(([count, orderSample], i) => (
-
+ {needsVerify && sample && (
+ 1
- ? `Part group ${i + 1}`
- : undefined
- }
+ verified={verifyAck}
+ stepLabel={showStepNumbers ? "Step 1" : undefined}
portfolioId={portfolioId}
uploadId={uploadId}
/>
- ))}
-
+ )}
+
+ {isAwaitingReview && unknownTotal > 0 && (
+
+ )}
+
+ {needsOrdering && orderingSamples.length > 0 && (
+
+ {orderingSamples.map(([count, orderSample], i) => (
+ 1
+ ? `Part group ${i + 1}`
+ : undefined
+ }
+ portfolioId={portfolioId}
+ uploadId={uploadId}
+ />
+ ))}
+
+ )}
+ >
+ )}
+
+ {showAddressTab && reviewTab === "addresses" && (
+
)}
{(canRunCombiner || isAwaitingReview) && (
@@ -284,11 +349,13 @@ export default function OnboardingProgress({
isPending={finalize.isPending}
disabled={!canFinalize}
disabledReason={
- unknownTotal > 0
- ? `Resolve ${unknownTotal} unclassified description${unknownTotal === 1 ? "" : "s"} first`
- : needsVerify && !verifyAck
- ? "Verify the classification first"
- : "Confirm the building-part order first"
+ unresolvedAddresses > 0
+ ? `Resolve ${unresolvedAddresses} address${unresolvedAddresses === 1 ? "" : "es"} on the Addresses tab first`
+ : unknownTotal > 0
+ ? `Resolve ${unknownTotal} unclassified description${unknownTotal === 1 ? "" : "s"} first`
+ : needsVerify && !verifyAck
+ ? "Verify the classification first"
+ : "Confirm the building-part order first"
}
onClick={() =>
finalize.mutate(undefined, { onSuccess: () => router.refresh() })
From 3f649755b470cb2e6c80ab8f18ac93e62a77e92d Mon Sep 17 00:00:00 2001
From: Jun-te Kim
Date: Tue, 7 Jul 2026 16:29:19 +0000
Subject: [PATCH 15/15] feat(portfolio): retire the Unmatched tab; surface
low_score (ADR-0057 Phase 4/5)
Phase 4: the portfolio landing page now renders the property table directly.
UPRN matching is confirmed during onboarding, so properties arrive matched and
the post-onboarding "Unmatched" tab is no longer needed (tab components left in
place pending a legacy-no-UPRN reconciliation follow-up).
Phase 5: AddressesPanel labels the new "low_score" status "Low-confidence match".
Co-Authored-By: Claude Opus 4.8 (1M context)
---
.../bulk-upload/[uploadId]/AddressesPanel.tsx | 2 ++
src/app/portfolio/[slug]/(portfolio)/page.tsx | 20 ++++++-------------
2 files changed, 8 insertions(+), 14 deletions(-)
diff --git a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx
index 6f99ab1e..cbff4d3f 100644
--- a/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/bulk-upload/[uploadId]/AddressesPanel.tsx
@@ -63,6 +63,8 @@ function statusLabel(status: string): string {
switch (status) {
case "ambiguous_duplicate":
return "Ambiguous match";
+ case "low_score":
+ return "Low-confidence match";
case "unmatched":
return "No match";
case "invalid_postcode":
diff --git a/src/app/portfolio/[slug]/(portfolio)/page.tsx b/src/app/portfolio/[slug]/(portfolio)/page.tsx
index 24bd0aba..c3305755 100644
--- a/src/app/portfolio/[slug]/(portfolio)/page.tsx
+++ b/src/app/portfolio/[slug]/(portfolio)/page.tsx
@@ -1,7 +1,4 @@
import PropertyTable from "../components/PropertyTable";
-import UnmatchedProperties from "../components/UnmatchedProperties";
-import PortfolioTabs from "../components/PortfolioTabs";
-import { getUnmatchedProperties } from "@/lib/properties/unmatched";
export const revalidate = 60;
@@ -14,15 +11,10 @@ export default async function Page(props: {
const params = await props.params;
const portfolioId = params.slug;
- const unmatched = await getUnmatchedProperties(portfolioId);
-
- return (
- }
- needsAttention={
-
- }
- />
- );
+ // UPRN matching is now confirmed during bulk-upload onboarding (ADR-0057), so
+ // properties arrive already matched — the portfolio-level "Unmatched" tab is
+ // retired. The tab components (PortfolioTabs / UnmatchedProperties /
+ // getUnmatchedProperties) are left in place for now, pending a follow-up that
+ // reconciles legacy no-UPRN properties onboarded before this feature.
+ return ;
}