Add EPC round-trip fidelity fixes and property_baseline_performance table to Drizzle schema

This commit is contained in:
Daniel Roth 2026-06-03 10:42:13 +00:00
parent 84aeeff117
commit 5553fee32e

View file

@ -10,6 +10,7 @@ import {
smallint,
bigint,
uniqueIndex,
jsonb,
} from "drizzle-orm/pg-core";
import { portfolio, PortfolioStatus } from "./portfolio";
import { InferModel } from "drizzle-orm";
@ -514,25 +515,25 @@ export const epcProperty = pgTable(
energyIsDwellingExportCapable: boolean("energy_is_dwelling_export_capable").notNull(),
energyWindTurbinesTerrainType: text("energy_wind_turbines_terrain_type").notNull(),
energyElectricitySmartMeterPresent: boolean("energy_electricity_smart_meter_present").notNull(),
energyPvConnection: text("energy_pv_connection"),
energyPvConnection: jsonb("energy_pv_connection"),
energyPvPercentRoofArea: integer("energy_pv_percent_roof_area"),
energyPvBatteryCapacity: real("energy_pv_battery_capacity"),
energyWindTurbineHubHeight: real("energy_wind_turbine_hub_height"),
energyWindTurbineRotorDiameter: real("energy_wind_turbine_rotor_diameter"),
// Heating config
heatingCylinderSize: text("heating_cylinder_size"),
heatingCylinderSize: jsonb("heating_cylinder_size"),
heatingWaterHeatingCode: integer("heating_water_heating_code"),
heatingWaterHeatingFuel: integer("heating_water_heating_fuel"),
heatingImmersionHeatingType: text("heating_immersion_heating_type"),
heatingCylinderInsulationType: text("heating_cylinder_insulation_type"),
heatingImmersionHeatingType: jsonb("heating_immersion_heating_type"),
heatingCylinderInsulationType: jsonb("heating_cylinder_insulation_type"),
heatingCylinderThermostat: text("heating_cylinder_thermostat"),
heatingSecondaryFuelType: integer("heating_secondary_fuel_type"),
heatingSecondaryHeatingType: text("heating_secondary_heating_type"),
heatingSecondaryHeatingType: jsonb("heating_secondary_heating_type"),
heatingCylinderInsulationThicknessMm: integer("heating_cylinder_insulation_thickness_mm"),
heatingWwhrsIndexNumber1: integer("heating_wwhrs_index_number_1"),
heatingWwhrsIndexNumber2: integer("heating_wwhrs_index_number_2"),
heatingShowerOutletType: text("heating_shower_outlet_type"),
heatingShowerOutletType: jsonb("heating_shower_outlet_type"),
heatingShowerWwhrs: integer("heating_shower_wwhrs"),
// Ventilation
@ -553,6 +554,27 @@ export const epcProperty = pgTable(
mechanicalVentDuctInsulation: integer("mechanical_vent_duct_insulation"),
mechanicalVentilationIndexNumber: integer("mechanical_ventilation_index_number"),
mechanicalVentMeasuredInstallation: text("mechanical_vent_measured_installation"),
mechanicalVentDuctInsulationLevel: integer("mechanical_vent_duct_insulation_level"),
// Addendum flags
addendumStoneWalls: boolean("addendum_stone_walls"),
addendumSystemBuild: boolean("addendum_system_build"),
addendumNumbers: jsonb("addendum_numbers"),
// Heating counts
heatingNumberBaths: integer("heating_number_baths"),
heatingNumberBathsWwhrs: integer("heating_number_baths_wwhrs"),
heatingElectricShowerCount: integer("heating_electric_shower_count"),
heatingMixerShowerCount: integer("heating_mixer_shower_count"),
// Ventilation detail
ventilationPresent: boolean("ventilation_present").notNull().default(false),
ventilationShelteredSides: integer("ventilation_sheltered_sides"),
ventilationHasSuspendedTimberFloor: boolean("ventilation_has_suspended_timber_floor"),
ventilationSuspendedTimberFloorSealed: boolean("ventilation_suspended_timber_floor_sealed"),
ventilationHasDraughtLobby: boolean("ventilation_has_draught_lobby"),
ventilationAirPermeabilityAp4M3HM2: real("ventilation_air_permeability_ap4_m3_h_m2"),
ventilationMechanicalVentilationKind: text("ventilation_mechanical_ventilation_kind"),
},
(table) => [
uniqueIndex("uq_epc_property_property_portfolio").on(
@ -627,10 +649,10 @@ export const epcMainHeatingDetail = pgTable(
.references(() => epcProperty.id),
hasFghrs: boolean("has_fghrs").notNull(),
mainFuelType: text("main_fuel_type").notNull(),
heatEmitterType: text("heat_emitter_type").notNull(),
emitterTemperature: text("emitter_temperature").notNull(),
mainHeatingControl: text("main_heating_control").notNull(),
mainFuelType: jsonb("main_fuel_type").notNull(),
heatEmitterType: jsonb("heat_emitter_type").notNull(),
emitterTemperature: jsonb("emitter_temperature").notNull(),
mainHeatingControl: jsonb("main_heating_control").notNull(),
fanFluePresent: boolean("fan_flue_present"),
boilerFlueType: integer("boiler_flue_type"),
boilerIgnitionType: integer("boiler_ignition_type"),
@ -661,10 +683,10 @@ export const epcBuildingPart = pgTable(
constructionAgeBand: text("construction_age_band").notNull(),
// Wall
wallConstruction: text("wall_construction").notNull(),
wallInsulationType: text("wall_insulation_type").notNull(),
wallConstruction: jsonb("wall_construction").notNull(),
wallInsulationType: jsonb("wall_insulation_type").notNull(),
wallThicknessMeasured: boolean("wall_thickness_measured").notNull(),
partyWallConstruction: text("party_wall_construction").notNull(),
partyWallConstruction: jsonb("party_wall_construction"),
buildingPartNumber: integer("building_part_number"),
wallDryLined: boolean("wall_dry_lined"),
wallThicknessMm: integer("wall_thickness_mm"),
@ -674,7 +696,7 @@ export const epcBuildingPart = pgTable(
// Floor
floorHeatLoss: integer("floor_heat_loss"),
floorInsulationThickness: text("floor_insulation_thickness"),
flatRoofInsulationThickness: text("flat_roof_insulation_thickness"),
flatRoofInsulationThickness: jsonb("flat_roof_insulation_thickness"),
floorType: text("floor_type"),
floorConstructionType: text("floor_construction_type"),
floorInsulationTypeStr: text("floor_insulation_type_str"),
@ -682,8 +704,11 @@ export const epcBuildingPart = pgTable(
// Roof
roofConstruction: integer("roof_construction"),
roofInsulationLocation: text("roof_insulation_location"),
roofInsulationThickness: text("roof_insulation_thickness"),
roofInsulationLocation: jsonb("roof_insulation_location"),
roofInsulationThickness: jsonb("roof_insulation_thickness"),
roofConstructionType: text("roof_construction_type"),
curtainWallAge: text("curtain_wall_age"),
// Room in roof (inlined)
roomInRoofFloorArea: real("room_in_roof_floor_area"),
@ -737,23 +762,23 @@ export const epcWindow = pgTable(
.notNull()
.references(() => epcProperty.id),
glazingGap: text("glazing_gap").notNull(),
orientation: text("orientation").notNull(),
windowType: text("window_type").notNull(),
glazingType: text("glazing_type").notNull(),
glazingGap: jsonb("glazing_gap").notNull(),
orientation: jsonb("orientation").notNull(),
windowType: jsonb("window_type").notNull(),
glazingType: jsonb("glazing_type").notNull(),
windowWidth: real("window_width").notNull(), // add unit?
windowHeight: real("window_height").notNull(), // add unit?
draughtProofed: boolean("draught_proofed").notNull(),
windowLocation: text("window_location").notNull(),
windowWallType: text("window_wall_type").notNull(),
permanentShuttersPresent: boolean("permanent_shutters_present").notNull(),
draughtProofed: jsonb("draught_proofed").notNull(),
windowLocation: jsonb("window_location").notNull(),
windowWallType: jsonb("window_wall_type").notNull(),
permanentShuttersPresent: jsonb("permanent_shutters_present").notNull(),
frameMaterial: text("frame_material"),
frameFactor: real("frame_factor"),
permanentShuttersInsulated: text("permanent_shutters_insulated"),
// Transmission details (inlined)
transmissionUValue: real("transmission_u_value"),
transmissionDataSource: text("transmission_data_source"),
transmissionDataSource: jsonb("transmission_data_source"),
transmissionSolarTransmittance: real("transmission_solar_transmittance"),
},
);
@ -773,4 +798,86 @@ export const epcEnergyElement = pgTable(
energyEfficiencyRating: integer("energy_efficiency_rating").notNull(),
environmentalEfficiencyRating: integer("environmental_efficiency_rating").notNull(),
},
);
// ─── epc_renewable_heat_incentive ─────────────────────────────────────────────
export const epcRenewableHeatIncentive = pgTable(
"epc_renewable_heat_incentive",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.unique()
.references(() => epcProperty.id),
spaceHeatingKwh: real("space_heating_kwh").notNull(),
waterHeatingKwh: real("water_heating_kwh").notNull(),
impactOfLoftInsulationKwh: real("impact_of_loft_insulation_kwh"),
impactOfCavityInsulationKwh: real("impact_of_cavity_insulation_kwh"),
impactOfSolidWallInsulationKwh: real("impact_of_solid_wall_insulation_kwh"),
},
);
// ─── property_baseline_performance ────────────────────────────────────────────
export const rebaselineReasonEnum = pgEnum("rebaseline_reason", [
"none",
"pre_sap10",
"physical_state_changed",
"both",
]);
export const propertyBaselinePerformance = pgTable(
"property_baseline_performance",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
propertyId: bigint("property_id", { mode: "bigint" })
.notNull()
.unique()
.references(() => property.id),
// Lodged performance (from gov EPC register)
lodgedSapScore: integer("lodged_sap_score").notNull(),
lodgedEpcBand: epcEnum("lodged_epc_band").notNull(),
lodgedCo2EmissionsTPerYr: real("lodged_co2_emissions_t_per_yr").notNull(),
lodgedPrimaryEnergyIntensityKwhPerM2Yr: integer(
"lodged_primary_energy_intensity_kwh_per_m2_yr",
).notNull(),
// Effective performance (what modelling scored against)
effectiveSapScore: integer("effective_sap_score").notNull(),
effectiveEpcBand: epcEnum("effective_epc_band").notNull(),
effectiveCo2EmissionsTPerYr: real(
"effective_co2_emissions_t_per_yr",
).notNull(),
effectivePrimaryEnergyIntensityKwhPerM2Yr: integer(
"effective_primary_energy_intensity_kwh_per_m2_yr",
).notNull(),
rebaselineReason: rebaselineReasonEnum("rebaseline_reason").notNull(),
// Interim energy demand (from EPC RHI data; superseded by bill block below once populated)
spaceHeatingKwh: real("space_heating_kwh").notNull(),
waterHeatingKwh: real("water_heating_kwh").notNull(),
// Bill block — nullable until BillDerivation wiring lands
fuelRatesPeriod: text("fuel_rates_period"),
heatingKwh: real("heating_kwh"),
heatingCostGbp: real("heating_cost_gbp"),
hotWaterKwh: real("hot_water_kwh"),
hotWaterCostGbp: real("hot_water_cost_gbp"),
lightingKwh: real("lighting_kwh"),
lightingCostGbp: real("lighting_cost_gbp"),
appliancesKwh: real("appliances_kwh"),
appliancesCostGbp: real("appliances_cost_gbp"),
cookingKwh: real("cooking_kwh"),
cookingCostGbp: real("cooking_cost_gbp"),
pumpsFansKwh: real("pumps_fans_kwh"),
pumpsFansCostGbp: real("pumps_fans_cost_gbp"),
coolingKwh: real("cooling_kwh"),
coolingCostGbp: real("cooling_cost_gbp"),
standingChargesGbp: real("standing_charges_gbp"),
segCreditGbp: real("seg_credit_gbp"),
totalAnnualBillGbp: real("total_annual_bill_gbp"),
},
);