Merge pull request #323 from Hestia-Homes/feature/more_property_overrides_table

db changes
This commit is contained in:
Jun-te Kim 2026-06-19 16:20:35 +01:00 committed by GitHub
commit 958c6e7c71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 68236 additions and 1 deletions

View file

@ -12,7 +12,7 @@
// Install Domna's curated skill set (pinned to 0.0.5) into this workspace,
// then install npm deps. `gh repo clone` handles private-repo auth using
// the mounted host ~/.config/gh.
"postCreateCommand": "gh repo clone Hestia-Homes/agentic-toolkit /tmp/agentic-toolkit -- --branch 0.0.5 --depth 1 && bash /tmp/agentic-toolkit/setup.sh && npm install",
"postCreateCommand": "gh repo clone Hestia-Homes/agentic-toolkit /tmp/agentic-toolkit -- --branch 0.0.8 --depth 1 && bash /tmp/agentic-toolkit/setup.sh && npm install",
"forwardPorts": ["frontend:3000", "pgadmin:80"],

View file

@ -0,0 +1,14 @@
CREATE TYPE "public"."main_fuel" AS ENUM('mains gas', 'mains gas (community)', 'electricity', 'LPG (bulk)', 'oil', 'house coal', 'Unknown');--> statement-breakpoint
ALTER TYPE "public"."override_component" ADD VALUE 'main_fuel';--> statement-breakpoint
CREATE TABLE "landlord_main_fuel_overrides" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"portfolio_id" bigint NOT NULL,
"description" text NOT NULL,
"value" "main_fuel" NOT NULL,
"source" "override_source" NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "landlord_main_fuel_overrides_portfolio_description_unique" UNIQUE("portfolio_id","description")
);
--> statement-breakpoint
ALTER TABLE "landlord_main_fuel_overrides" ADD CONSTRAINT "landlord_main_fuel_overrides_portfolio_id_portfolio_id_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE cascade ON UPDATE no action;

View file

@ -0,0 +1,14 @@
CREATE TYPE "public"."glazing" AS ENUM('Single glazing', 'Double glazing, 2002 or later', 'Double glazing, pre-2002', 'Triple glazing, pre-2002', 'Triple glazing, 2002 or later', 'Unknown');--> statement-breakpoint
ALTER TYPE "public"."override_component" ADD VALUE 'glazing';--> statement-breakpoint
CREATE TABLE "landlord_glazing_overrides" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"portfolio_id" bigint NOT NULL,
"description" text NOT NULL,
"value" "glazing" NOT NULL,
"source" "override_source" NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "landlord_glazing_overrides_portfolio_description_unique" UNIQUE("portfolio_id","description")
);
--> statement-breakpoint
ALTER TABLE "landlord_glazing_overrides" ADD CONSTRAINT "landlord_glazing_overrides_portfolio_id_portfolio_id_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE cascade ON UPDATE no action;

View file

@ -0,0 +1,14 @@
CREATE TYPE "public"."construction_age_band" AS ENUM('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'Unknown');--> statement-breakpoint
ALTER TYPE "public"."override_component" ADD VALUE 'construction_age_band';--> statement-breakpoint
CREATE TABLE "landlord_construction_age_band_overrides" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"portfolio_id" bigint NOT NULL,
"description" text NOT NULL,
"value" "construction_age_band" NOT NULL,
"source" "override_source" NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "landlord_construction_age_band_portfolio_description_unique" UNIQUE("portfolio_id","description")
);
--> statement-breakpoint
ALTER TABLE "landlord_construction_age_band_overrides" ADD CONSTRAINT "landlord_construction_age_band_overrides_portfolio_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE cascade ON UPDATE no action;

View file

@ -0,0 +1,28 @@
CREATE TYPE "public"."main_heating_system" AS ENUM('Gas boiler, combi', 'Gas boiler, regular', 'Gas CPSU', 'Electric storage heaters, fan', 'Direct-acting electric', 'Unknown');--> statement-breakpoint
CREATE TYPE "public"."water_heating" AS ENUM('From main system, mains gas', 'From main system, electricity', 'Electric immersion, electricity', 'Unknown');--> statement-breakpoint
ALTER TYPE "public"."override_component" ADD VALUE 'water_heating';--> statement-breakpoint
ALTER TYPE "public"."override_component" ADD VALUE 'main_heating_system';--> statement-breakpoint
CREATE TABLE "landlord_main_heating_system_overrides" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"portfolio_id" bigint NOT NULL,
"description" text NOT NULL,
"value" "main_heating_system" NOT NULL,
"source" "override_source" NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "landlord_main_heating_system_portfolio_description_unique" UNIQUE("portfolio_id","description")
);
--> statement-breakpoint
CREATE TABLE "landlord_water_heating_overrides" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"portfolio_id" bigint NOT NULL,
"description" text NOT NULL,
"value" "water_heating" NOT NULL,
"source" "override_source" NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "landlord_water_heating_overrides_portfolio_description_unique" UNIQUE("portfolio_id","description")
);
--> statement-breakpoint
ALTER TABLE "landlord_main_heating_system_overrides" ADD CONSTRAINT "landlord_main_heating_system_overrides_portfolio_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "landlord_water_heating_overrides" ADD CONSTRAINT "landlord_water_heating_overrides_portfolio_id_portfolio_id_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE cascade ON UPDATE no action;

View file

@ -0,0 +1,6 @@
ALTER TYPE "public"."main_fuel" ADD VALUE 'electricity (community)' BEFORE 'LPG (bulk)';--> statement-breakpoint
ALTER TYPE "public"."main_fuel" ADD VALUE 'bottled LPG' BEFORE 'oil';--> statement-breakpoint
ALTER TYPE "public"."main_fuel" ADD VALUE 'LPG special condition' BEFORE 'oil';--> statement-breakpoint
ALTER TYPE "public"."main_fuel" ADD VALUE 'smokeless coal' BEFORE 'Unknown';--> statement-breakpoint
ALTER TYPE "public"."main_fuel" ADD VALUE 'dual fuel (mineral and wood)' BEFORE 'Unknown';--> statement-breakpoint
ALTER TYPE "public"."main_fuel" ADD VALUE 'biomass (community)' BEFORE 'Unknown';

View file

@ -0,0 +1,8 @@
ALTER TYPE "public"."main_heating_system" ADD VALUE 'Electric storage heaters, old' BEFORE 'Electric storage heaters, fan';--> statement-breakpoint
ALTER TYPE "public"."main_heating_system" ADD VALUE 'Electric storage heaters, slimline' BEFORE 'Electric storage heaters, fan';--> statement-breakpoint
ALTER TYPE "public"."main_heating_system" ADD VALUE 'Electric storage heaters, convector' BEFORE 'Electric storage heaters, fan';--> statement-breakpoint
ALTER TYPE "public"."water_heating" ADD VALUE 'From main system, oil' BEFORE 'Electric immersion, electricity';--> statement-breakpoint
ALTER TYPE "public"."water_heating" ADD VALUE 'From main system, LPG (bulk)' BEFORE 'Electric immersion, electricity';--> statement-breakpoint
ALTER TYPE "public"."water_heating" ADD VALUE 'From main system, bottled LPG' BEFORE 'Electric immersion, electricity';--> statement-breakpoint
ALTER TYPE "public"."water_heating" ADD VALUE 'From main system, house coal' BEFORE 'Electric immersion, electricity';--> statement-breakpoint
ALTER TYPE "public"."water_heating" ADD VALUE 'Gas boiler/circulator, mains gas' BEFORE 'Unknown';

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1639,6 +1639,48 @@
"when": 1781617412914,
"tag": "0234_old_rachel_grey",
"breakpoints": true
},
{
"idx": 235,
"version": "7",
"when": 1781874717696,
"tag": "0235_orange_pixie",
"breakpoints": true
},
{
"idx": 236,
"version": "7",
"when": 1781876323316,
"tag": "0236_overrated_human_cannonball",
"breakpoints": true
},
{
"idx": 237,
"version": "7",
"when": 1781877302435,
"tag": "0237_youthful_mulholland_black",
"breakpoints": true
},
{
"idx": 238,
"version": "7",
"when": 1781878257041,
"tag": "0238_awesome_kylun",
"breakpoints": true
},
{
"idx": 239,
"version": "7",
"when": 1781878752684,
"tag": "0239_bumpy_blob",
"breakpoints": true
},
{
"idx": 240,
"version": "7",
"when": 1781879071017,
"tag": "0240_petite_jamie_braddock",
"breakpoints": true
}
]
}

View file

@ -1,5 +1,6 @@
import {
bigint,
foreignKey,
pgEnum,
pgTable,
text,
@ -132,6 +133,89 @@ export const RoofTypeValues: [string, ...string[]] = [
"Unknown",
];
// Mirrors MainFuelType.value (domain/epc/main_fuel_type.py). Each value is the
// canonical fuel description the main-fuel Simulation Overlay decomposes into the
// RdSAP main_fuel int code. Keep in sync — see docs/adr/0002-landlord-override-vocabulary.md.
export const MainFuelValues: [string, ...string[]] = [
"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)",
"Unknown",
];
// Mirrors GlazingType.value (domain/epc/glazing_type.py). Each value is the
// canonical glazing description (type + era) the glazing Simulation Overlay
// decomposes into the SAP10 glazing_type code. Keep in sync — see
// docs/adr/0002-landlord-override-vocabulary.md.
export const GlazingValues: [string, ...string[]] = [
"Single glazing",
"Double glazing, 2002 or later",
"Double glazing, pre-2002",
"Triple glazing, pre-2002",
"Triple glazing, 2002 or later",
"Unknown",
];
// Mirrors ConstructionAgeBand.value (domain/epc/construction_age_band.py): the
// RdSAP England-&-Wales age-band letter codes A..M the calculator reads. Keep in
// sync — see docs/adr/0002-landlord-override-vocabulary.md.
export const ConstructionAgeBandValues: [string, ...string[]] = [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"Unknown",
];
// Mirrors WaterHeatingType.value (domain/epc/water_heating_type.py): "<system>,
// <fuel>" descriptions the water-heating overlay decomposes into
// water_heating_code + water_heating_fuel. Keep in sync — see
// docs/adr/0002-landlord-override-vocabulary.md.
export const WaterHeatingValues: [string, ...string[]] = [
"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",
"Unknown",
];
// Mirrors MainHeatingSystemType.value (domain/epc/main_heating_system_type.py):
// system archetypes the main-heating overlay maps to a sap_main_heating_code.
// Keep in sync — see docs/adr/0002-landlord-override-vocabulary.md.
export const MainHeatingSystemValues: [string, ...string[]] = [
"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",
"Unknown",
];
export const OverrideSourceValues: [string, ...string[]] = [
"classifier",
"user",
@ -141,6 +225,17 @@ export const propertyTypeEnum = pgEnum("property_type", PropertyTypeValues);
export const wallTypeEnum = pgEnum("wall_type", WallTypeValues);
export const builtFormTypeEnum = pgEnum("built_form_type", BuiltFormTypeValues);
export const roofTypeEnum = pgEnum("roof_type", RoofTypeValues);
export const mainFuelEnum = pgEnum("main_fuel", MainFuelValues);
export const glazingEnum = pgEnum("glazing", GlazingValues);
export const constructionAgeBandEnum = pgEnum(
"construction_age_band",
ConstructionAgeBandValues,
);
export const waterHeatingEnum = pgEnum("water_heating", WaterHeatingValues);
export const mainHeatingSystemEnum = pgEnum(
"main_heating_system",
MainHeatingSystemValues,
);
export const overrideSourceEnum = pgEnum(
"override_source",
OverrideSourceValues,
@ -245,3 +340,143 @@ export const landlordRoofTypeOverrides = pgTable(
).on(table.portfolioId, table.description),
}),
);
export const landlordMainFuelOverrides = pgTable(
"landlord_main_fuel_overrides",
{
id: uuid("id").defaultRandom().primaryKey(),
portfolioId: bigint("portfolio_id", { mode: "bigint" })
.notNull()
.references(() => portfolio.id, { onDelete: "cascade" }),
description: text("description").notNull(),
value: mainFuelEnum("value").notNull(),
source: overrideSourceEnum("source").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdate(() => new Date()),
},
(table) => ({
portfolioDescriptionUnique: unique(
"landlord_main_fuel_overrides_portfolio_description_unique",
).on(table.portfolioId, table.description),
}),
);
export const landlordGlazingOverrides = pgTable(
"landlord_glazing_overrides",
{
id: uuid("id").defaultRandom().primaryKey(),
portfolioId: bigint("portfolio_id", { mode: "bigint" })
.notNull()
.references(() => portfolio.id, { onDelete: "cascade" }),
description: text("description").notNull(),
value: glazingEnum("value").notNull(),
source: overrideSourceEnum("source").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdate(() => new Date()),
},
(table) => ({
portfolioDescriptionUnique: unique(
"landlord_glazing_overrides_portfolio_description_unique",
).on(table.portfolioId, table.description),
}),
);
export const landlordConstructionAgeBandOverrides = pgTable(
"landlord_construction_age_band_overrides",
{
id: uuid("id").defaultRandom().primaryKey(),
// FK defined in the table config below with an explicit short name (the
// default derived name overflows Postgres's 63-char identifier limit).
portfolioId: bigint("portfolio_id", { mode: "bigint" }).notNull(),
description: text("description").notNull(),
value: constructionAgeBandEnum("value").notNull(),
source: overrideSourceEnum("source").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdate(() => new Date()),
},
(table) => ({
// Both names shortened to stay within PostgreSQL's 63-char identifier limit
// (the default derived names are 68/69 chars); mirror
// LandlordConstructionAgeBandOverrideRow.
portfolioFk: foreignKey({
columns: [table.portfolioId],
foreignColumns: [portfolio.id],
name: "landlord_construction_age_band_overrides_portfolio_fk",
}).onDelete("cascade"),
portfolioDescriptionUnique: unique(
"landlord_construction_age_band_portfolio_description_unique",
).on(table.portfolioId, table.description),
}),
);
export const landlordWaterHeatingOverrides = pgTable(
"landlord_water_heating_overrides",
{
id: uuid("id").defaultRandom().primaryKey(),
portfolioId: bigint("portfolio_id", { mode: "bigint" })
.notNull()
.references(() => portfolio.id, { onDelete: "cascade" }),
description: text("description").notNull(),
value: waterHeatingEnum("value").notNull(),
source: overrideSourceEnum("source").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdate(() => new Date()),
},
(table) => ({
portfolioDescriptionUnique: unique(
"landlord_water_heating_overrides_portfolio_description_unique",
).on(table.portfolioId, table.description),
}),
);
export const landlordMainHeatingSystemOverrides = pgTable(
"landlord_main_heating_system_overrides",
{
id: uuid("id").defaultRandom().primaryKey(),
// FK in the table config below with an explicit short name (the default
// derived name overflows Postgres's 63-char identifier limit).
portfolioId: bigint("portfolio_id", { mode: "bigint" }).notNull(),
description: text("description").notNull(),
value: mainHeatingSystemEnum("value").notNull(),
source: overrideSourceEnum("source").notNull(),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdate(() => new Date()),
},
(table) => ({
// Both names shortened to stay within Postgres's 63-char identifier limit;
// mirror LandlordMainHeatingSystemOverrideRow.
portfolioFk: foreignKey({
columns: [table.portfolioId],
foreignColumns: [portfolio.id],
name: "landlord_main_heating_system_overrides_portfolio_fk",
}).onDelete("cascade"),
portfolioDescriptionUnique: unique(
"landlord_main_heating_system_portfolio_description_unique",
).on(table.portfolioId, table.description),
}),
);

View file

@ -26,6 +26,11 @@ export const OverrideComponentValues: [string, ...string[]] = [
"roof_type",
"property_type",
"built_form_type",
"main_fuel",
"glazing",
"construction_age_band",
"water_heating",
"main_heating_system",
];
export const overrideComponentEnum = pgEnum(

View file

@ -31,6 +31,11 @@ export const INTERNAL_FIELDS: InternalField[] = [
{ value: "built_form_type", label: "Built Form", kind: "classifier", required: false },
{ value: "wall_type", label: "Wall Type", kind: "classifier", required: false },
{ value: "roof_type", label: "Roof Type", kind: "classifier", required: false },
{ value: "main_fuel", label: "Main Fuel", kind: "classifier", required: false },
{ value: "glazing", label: "Glazing", kind: "classifier", required: false },
{ value: "construction_age_band", label: "Construction Age Band", kind: "classifier", required: false },
{ value: "water_heating", label: "Hot Water", kind: "classifier", required: false },
{ value: "main_heating_system", label: "Main Heating", kind: "classifier", required: false },
];
export const ADDRESS_FIELDS = INTERNAL_FIELDS.filter((f) => f.kind === "address");