tables for new epc data

This commit is contained in:
Daniel Roth 2026-04-23 15:49:53 +00:00
parent c8380f513c
commit 2960b99c8b

View file

@ -162,7 +162,120 @@ export const FeatureRating: [string, ...string[]] = [
"Poor",
"Very poor",
"N/A",
];
];export const propertyDetailsEpc = pgTable(
"property_details_epc",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
propertyId: bigint("property_id", { mode: "bigint" })
.notNull()
.references(() => property.id),
portfolioId: bigint("portfolio_id", { mode: "bigint" })
.notNull()
.references(() => portfolio.id),
fullAddress: text("full_address"),
// Date the EPC was lodged
lodgementDate: timestamp("lodgement_date"),
isExpired: boolean("is_expired"),
totalFloorArea: real("total_floor_area"),
walls: text("walls"),
wallsRating: smallint("walls_rating"),
roof: text("roof"),
roofRating: smallint("roof_rating"),
floor: text("floor"),
floorRating: smallint("floor_rating"),
windows: text("windows"),
windowsRating: smallint("windows_rating"),
heating: text("heating"),
heatingRating: smallint("heating_rating"),
heatingControls: text("heating_controls"),
heatingControlsRating: smallint("heating_controls_rating"),
hotWater: text("hot_water"),
hotWaterRating: smallint("hot_water_rating"),
lighting: text("lighting"),
lightingRating: smallint("lighting_rating"),
mainfuel: text("mainfuel"),
ventilation: text("ventilation"),
solarPv: real("solar_pv"),
solarHotWater: boolean("solar_hot_water"),
windTurbine: smallint("wind_turbine"),
floorHeight: real("floor_height"),
numberHeatedRooms: integer("number_heated_rooms"),
heatLossCorridor: boolean("heat_loss_corridor"),
unheatedCorridorLength: real("unheated_corridor_length"),
numberOpenFireplaces: integer("number_of_open_fireplaces"),
numberExtensions: integer("number_of_extensions"),
numberStoreys: integer("number_of_storeys"),
mainsGas: boolean("mains_gas"),
energyTariff: text("energy_tariff"),
// This is heat demand
primaryEnergyConsumption: real("primary_energy_consumption"),
co2Emissions: real("co2_emissions"),
// Bad naming but currentEnergyDemand is the current kwh consumption - needs to be renamed
currentEnergyDemand: real("current_energy_demand"),
currentEnergyDemandHeatingHotwater: real(
"current_energy_demand_heating_hotwater",
),
estimated: boolean("estimated").default(false),
// We indicate if the property has an overwritten SAP 05 EPC. I.e. there is a valid EPC, however it's a SAP 05
// EPC which isn't particularly useful. This value is defaulted to False
sap05Overwritten: boolean("sap_05_overwritten").default(false),
// When we've overwritten a SAP 05 EPC, we store the SAP 05 score and rating here for reference
sap05Score: real("sap_05_score"),
sap05EpcRating: epcEnum("sap_05_epc_rating"),
// Include current estimates for energy bills, across the different types of energy
// These predictions are based on the EPC predicted consumptions + current energy prices
heatingEnergyCostCurrent: real("heating_cost_current"),
hotWaterEnergyCostCurrent: real("hot_water_cost_current"),
lightingEnergyCostCurrent: real("lighting_cost_current"),
appliancesEnergyCostCurrent: real("appliances_cost_current"),
gasStandingCharge: real("gas_standing_charge"),
electricityStandingCharge: real("electricity_standing_charge"),
// When we have already installed measures, we will adjust the carbon, bills, kwh, heat demandto reflect this. We keep a record of
// 1) The adjustments
// 2) original values
// 3) a flag to indicate whether the values have been adjusted, for easily filtering
// original values - we don't need bills because we don't actually adjust any of the originals we just subtract adjustments from current values
// TODO - deprecate
originalCo2Emissions: real("original_co2_emissions"),
originalPrimaryEnergyConsumption: real(
"original_primary_energy_consumption",
),
originalCurrentEnergyDemand: real("original_current_energy_demand"),
originalCurrentEnergyDemandHeatingHotwater: real(
"original_current_energy_demand_heating_hotwater",
),
// adjustment quantities - TODO: deprecate
installedMeasuresCo2Adjustment: real("installed_measures_co2_adjustment"),
installedMeasuresEnergyDemandAdjustment: real(
"installed_measures_energy_demand_adjustment",
),
installedMeasuresTotalEnergyBillAdjustment: real(
"installed_measures_total_energy_bill_adjustment",
),
installedMeasuresHeatDemandAdjustment: real(
"installed_measures_heat_demand_adjustment",
),
isEpcAdjustedForInstalledMeasures: boolean(
"is_epc_adjusted_for_installed_measures",
).default(false),
// Lodged values
lodgedCo2Emissions: real("lodged_co2_emissions"),
lodgedHeatDemand: real("lodged_heat_demand"),
hasBeenRemodelled: boolean("has_been_remodelled").default(false),
// additional fields
environment_impact_current: real("environment_impact_current"),
},
(table) => [
uniqueIndex("uq_property_details_epc_property_portfolio").on(
table.propertyId,
table.portfolioId,
),
],
);
export const FeatureRatingNumeric: [number, ...number[]] = [5, 4, 3, 2, 1];
@ -398,3 +511,370 @@ export interface NonIntrusiveSurveyData {
surveyor: string;
notes: { title: string; note: string }[];
}
// ─── Enums ────────────────────────────────────────────────────────────────────
export const energyElementTypeEnum = pgEnum("energy_element_type", [
"roof", "wall", "floor", "main_heating", "window",
"lighting", "hot_water", "secondary_heating", "main_heating_controls",
]);
// ─── epc_property ─────────────────────────────────────────────────────────────
export const epcProperty = pgTable(
"epc_property",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
propertyId: bigint("property_id", { mode: "bigint" })
.notNull()
.references(() => property.id),
portfolioId: bigint("portfolio_id", { mode: "bigint" })
.notNull()
.references(() => portfolio.id),
// Identity / admin
uprn: bigint("uprn", { mode: "bigint" }),
uprnSource: text("uprn_source"),
reportReference: text("report_reference"),
reportType: text("report_type"),
assessmentType: text("assessment_type"),
sapVersion: real("sap_version"),
schemaType: text("schema_type"),
schemaVersionsOriginal: text("schema_versions_original"),
status: text("status"),
calculationSoftwareVersion: text("calculation_software_version"),
// Address
addressLine1: text("address_line_1"),
addressLine2: text("address_line_2"),
postTown: text("post_town"),
postcode: text("postcode"),
regionCode: text("region_code"),
countryCode: text("country_code"),
languageCode: text("language_code"),
// Property description
dwellingType: text("dwelling_type").notNull(),
propertyType: text("property_type"),
builtForm: text("built_form"),
tenure: text("tenure").notNull(),
transactionType: text("transaction_type").notNull(),
// Dates
inspectionDate: timestamp("inspection_date").notNull(),
completionDate: timestamp("completion_date"),
registrationDate: timestamp("registration_date"),
// Measurements
totalFloorAreaM2: real("total_floor_area_m2").notNull(),
measurementType: integer("measurement_type"),
// Flags
solarWaterHeating: boolean("solar_water_heating").notNull(),
hasHotWaterCylinder: boolean("has_hot_water_cylinder").notNull(),
hasFixedAirConditioning: boolean("has_fixed_air_conditioning").notNull(),
hasConservatory: boolean("has_conservatory"),
hasHeatedSeparateConservatory: boolean("has_heated_separate_conservatory"),
conservatoryType: integer("conservatory_type"),
// Counts
doorCount: integer("door_count").notNull(),
wetRoomsCount: integer("wet_rooms_count").notNull(),
extensionsCount: integer("extensions_count").notNull(),
heatedRoomsCount: integer("heated_rooms_count").notNull(),
openChimneysCount: integer("open_chimneys_count").notNull(),
habitableRoomsCount: integer("habitable_rooms_count").notNull(),
insulatedDoorCount: integer("insulated_door_count").notNull(),
cflFixedLightingBulbsCount: integer("cfl_fixed_lighting_bulbs_count").notNull(),
ledFixedLightingBulbsCount: integer("led_fixed_lighting_bulbs_count").notNull(),
incandescentFixedLightingBulbsCount: integer("incandescent_fixed_lighting_bulbs_count").notNull(),
blockedChimneysCount: integer("blocked_chimneys_count"),
draughtproofedDoorCount: integer("draughtproofed_door_count"),
energyRatingAverage: integer("energy_rating_average"),
lowEnergyFixedLightingBulbsCount: integer("low_energy_fixed_lighting_bulbs_count"),
fixedLightingOutletsCount: integer("fixed_lighting_outlets_count"),
lowEnergyFixedLightingOutletsCount: integer("low_energy_fixed_lighting_outlets_count"),
numberOfStoreys: integer("number_of_storeys"),
anyUnheatedRooms: boolean("any_unheated_rooms"),
// Misc
hydro: boolean("hydro"),
photovoltaicArray: boolean("photovoltaic_array"),
wasteWaterHeatRecovery: text("waste_water_heat_recovery"),
pressureTest: integer("pressure_test"),
pressureTestCertificateNumber: integer("pressure_test_certificate_number"),
percentDraughtproofed: integer("percent_draughtproofed"),
insulatedDoorUValue: real("insulated_door_u_value"),
multipleGlazedProportion: integer("multiple_glazed_proportion"),
windowsTransmissionUValue: real("windows_transmission_u_value"),
windowsTransmissionDataSource: integer("windows_transmission_data_source"),
windowsTransmissionSolarTransmittance: real("windows_transmission_solar_transmittance"),
// Energy source
energyMainsGas: boolean("energy_mains_gas").notNull(),
energyMeterType: text("energy_meter_type").notNull(),
energyPvBatteryCount: integer("energy_pv_battery_count").notNull(),
energyWindTurbinesCount: integer("energy_wind_turbines_count").notNull(),
energyGasSmartMeterPresent: boolean("energy_gas_smart_meter_present").notNull(),
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"),
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"),
heatingWaterHeatingCode: integer("heating_water_heating_code"),
heatingWaterHeatingFuel: integer("heating_water_heating_fuel"),
heatingImmersionHeatingType: text("heating_immersion_heating_type"),
heatingCylinderInsulationType: text("heating_cylinder_insulation_type"),
heatingCylinderThermostat: text("heating_cylinder_thermostat"),
heatingSecondaryFuelType: integer("heating_secondary_fuel_type"),
heatingSecondaryHeatingType: text("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"),
heatingShowerWwhrs: integer("heating_shower_wwhrs"),
// Ventilation
ventilationType: text("ventilation_type"),
ventilationDraughtLobby: boolean("ventilation_draught_lobby"),
ventilationPressureTest: text("ventilation_pressure_test"),
ventilationOpenFluesCount: integer("ventilation_open_flues_count"),
ventilationClosedFluesCount: integer("ventilation_closed_flues_count"),
ventilationBoilerFluesCount: integer("ventilation_boiler_flues_count"),
ventilationOtherFluesCount: integer("ventilation_other_flues_count"),
ventilationExtractFansCount: integer("ventilation_extract_fans_count"),
ventilationPassiveVentsCount: integer("ventilation_passive_vents_count"),
ventilationFluelessGasFiresCount: integer("ventilation_flueless_gas_fires_count"),
ventilationInPcdfDatabase: boolean("ventilation_in_pcdf_database"),
mechanicalVentilation: integer("mechanical_ventilation"),
mechanicalVentDuctType: integer("mechanical_vent_duct_type"),
mechanicalVentDuctPlacement: integer("mechanical_vent_duct_placement"),
mechanicalVentDuctInsulation: integer("mechanical_vent_duct_insulation"),
mechanicalVentilationIndexNumber: integer("mechanical_ventilation_index_number"),
mechanicalVentMeasuredInstallation: text("mechanical_vent_measured_installation"),
},
(table) => [
uniqueIndex("uq_epc_property_property_portfolio").on(
table.propertyId,
table.portfolioId,
),
],
);
// ─── epc_property_energy_performance ─────────────────────────────────────────
export const epcPropertyEnergyPerformance = pgTable(
"epc_property_energy_performance",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.unique()
.references(() => epcProperty.id),
// Current
energyRatingCurrent: integer("energy_rating_current"),
energyConsumptionCurrent: integer("energy_consumption_current"),
environmentalImpactCurrent: integer("environmental_impact_current"),
heatingCostCurrent: real("heating_cost_current"),
lightingCostCurrent: real("lighting_cost_current"),
hotWaterCostCurrent: real("hot_water_cost_current"),
co2EmissionsCurrent: real("co2_emissions_current"),
co2EmissionsCurrentPerFloorArea: integer("co2_emissions_current_per_floor_area"),
currentEnergyEfficiencyBand: text("current_energy_efficiency_band"),
// Potential
energyRatingPotential: real("energy_rating_potential"),
energyConsumptionPotential: integer("energy_consumption_potential"),
environmentalImpactPotential: integer("environmental_impact_potential"),
heatingCostPotential: real("heating_cost_potential"),
lightingCostPotential: real("lighting_cost_potential"),
hotWaterCostPotential: real("hot_water_cost_potential"),
co2EmissionsPotential: real("co2_emissions_potential"),
potentialEnergyEfficiencyBand: text("potential_energy_efficiency_band"),
},
);
// ─── epc_flat_details ─────────────────────────────────────────────────────────
export const epcFlatDetails = pgTable(
"epc_flat_details",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.unique()
.references(() => epcProperty.id),
level: integer("level").notNull(),
topStorey: text("top_storey").notNull(),
flatLocation: integer("flat_location").notNull(),
heatLossCorridor: integer("heat_loss_corridor").notNull(),
storeyCount: integer("storey_count"),
unheatedCorridorLengthM: integer("unheated_corridor_length_m"),
},
);
// ─── epc_main_heating_detail ──────────────────────────────────────────────────
export const epcMainHeatingDetail = pgTable(
"epc_main_heating_detail",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.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(),
fanFluePresent: boolean("fan_flue_present"),
boilerFlueType: integer("boiler_flue_type"),
boilerIgnitionType: integer("boiler_ignition_type"),
centralHeatingPumpAge: integer("central_heating_pump_age"),
centralHeatingPumpAgeStr: text("central_heating_pump_age_str"),
mainHeatingIndexNumber: integer("main_heating_index_number"),
sapMainHeatingCode: integer("sap_main_heating_code"),
mainHeatingNumber: integer("main_heating_number"),
mainHeatingCategory: integer("main_heating_category"),
mainHeatingFraction: integer("main_heating_fraction"),
mainHeatingDataSource: integer("main_heating_data_source"),
condensing: boolean("condensing"),
weatherCompensator: boolean("weather_compensator"),
},
);
// ─── epc_building_part ────────────────────────────────────────────────────────
export const epcBuildingPart = pgTable(
"epc_building_part",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.references(() => epcProperty.id),
identifier: text("identifier").notNull(),
constructionAgeBand: text("construction_age_band").notNull(),
// Wall
wallConstruction: text("wall_construction").notNull(),
wallInsulationType: text("wall_insulation_type").notNull(),
wallThicknessMeasured: boolean("wall_thickness_measured").notNull(),
partyWallConstruction: text("party_wall_construction").notNull(),
buildingPartNumber: integer("building_part_number"),
wallDryLined: boolean("wall_dry_lined"),
wallThicknessMm: integer("wall_thickness_mm"),
wallInsulationThickness: text("wall_insulation_thickness"),
// Floor
floorHeatLoss: integer("floor_heat_loss"),
floorInsulationThickness: text("floor_insulation_thickness"),
flatRoofInsulationThickness: text("flat_roof_insulation_thickness"),
floorType: text("floor_type"),
floorConstructionType: text("floor_construction_type"),
floorInsulationTypeStr: text("floor_insulation_type_str"),
floorUValueKnown: boolean("floor_u_value_known"),
// Roof
roofConstruction: integer("roof_construction"),
roofInsulationLocation: text("roof_insulation_location"),
roofInsulationThickness: text("roof_insulation_thickness"),
// Room in roof (inlined)
roomInRoofFloorArea: real("room_in_roof_floor_area"),
roomInRoofConstructionAgeBand: text("room_in_roof_construction_age_band"),
// Alternative wall 1 (inlined)
altWall1Area: real("alt_wall_1_area"),
altWall1DryLined: text("alt_wall_1_dry_lined"),
altWall1Construction: integer("alt_wall_1_construction"),
altWall1InsulationType: integer("alt_wall_1_insulation_type"),
altWall1ThicknessMeasured: text("alt_wall_1_thickness_measured"),
altWall1InsulationThickness: text("alt_wall_1_insulation_thickness"),
// Alternative wall 2 (inlined)
altWall2Area: real("alt_wall_2_area"),
altWall2DryLined: text("alt_wall_2_dry_lined"),
altWall2Construction: integer("alt_wall_2_construction"),
altWall2InsulationType: integer("alt_wall_2_insulation_type"),
altWall2ThicknessMeasured: text("alt_wall_2_thickness_measured"),
altWall2InsulationThickness: text("alt_wall_2_insulation_thickness"),
},
);
// ─── epc_floor_dimension ──────────────────────────────────────────────────────
export const epcFloorDimension = pgTable(
"epc_floor_dimension",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcBuildingPartId: bigint("epc_building_part_id", { mode: "bigint" })
.notNull()
.references(() => epcBuildingPart.id),
floor: integer("floor"),
roomHeightM: real("room_height_m").notNull(),
totalFloorAreaM2: real("total_floor_area_m2").notNull(),
partyWallLengthM: real("party_wall_length_m").notNull(),
heatLossPerimeterM: real("heat_loss_perimeter_m").notNull(),
floorInsulation: integer("floor_insulation"),
floorConstruction: integer("floor_construction"),
},
);
// ─── epc_window ───────────────────────────────────────────────────────────────
export const epcWindow = pgTable(
"epc_window",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.references(() => epcProperty.id),
pvcFrame: text("pvc_frame").notNull(),
glazingGap: text("glazing_gap").notNull(),
orientation: text("orientation").notNull(),
windowType: text("window_type").notNull(),
glazingType: text("glazing_type").notNull(),
windowWidth: real("window_width").notNull(),
windowHeight: real("window_height").notNull(),
draughtProofed: boolean("draught_proofed").notNull(),
windowLocation: text("window_location").notNull(),
windowWallType: text("window_wall_type").notNull(),
permanentShuttersPresent: boolean("permanent_shutters_present").notNull(),
frameFactor: real("frame_factor"),
permanentShuttersInsulated: text("permanent_shutters_insulated"),
// Transmission details (inlined)
transmissionUValue: real("transmission_u_value"),
transmissionDataSource: integer("transmission_data_source"),
transmissionSolarTransmittance: real("transmission_solar_transmittance"),
},
);
// ─── epc_energy_element ───────────────────────────────────────────────────────
export const epcEnergyElement = pgTable(
"epc_energy_element",
{
id: bigserial("id", { mode: "bigint" }).primaryKey(),
epcPropertyId: bigint("epc_property_id", { mode: "bigint" })
.notNull()
.references(() => epcProperty.id),
elementType: energyElementTypeEnum("element_type").notNull(),
description: text("description").notNull(),
energyEfficiencyRating: integer("energy_efficiency_rating").notNull(),
environmentalEfficiencyRating: integer("environmental_efficiency_rating").notNull(),
},
);