mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
feat: persist 7 calculator-read EPC fields 🟩
Wire community heating fuel + CHP fraction (epc_main_heating_detail), alt-wall is_sheltered + wall insulation thermal conductivity (epc_building_part), and pv_diverter_present / measured cylinder volume / AP50 air permeability (epc_property) through save + _compose/_to_*. All deep-equal round-trip; coverage guard now enforces their reconstruction. Columns live (FE migration applied). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5c4a8d9094
commit
513c9b9897
2 changed files with 36 additions and 0 deletions
|
|
@ -139,6 +139,9 @@ class EpcPropertyModel(SQLModel, table=True):
|
|||
)
|
||||
energy_pv_percent_roof_area: Optional[int] = Field(default=None)
|
||||
energy_pv_battery_capacity: Optional[float] = Field(default=None)
|
||||
# An EV/PV diverter present on the dwelling (SAP §M; the calculator reads it
|
||||
# via cert_to_inputs). Non-optional bool defaulting False, matching the domain.
|
||||
energy_pv_diverter_present: bool = Field(default=False)
|
||||
energy_wind_turbine_hub_height: Optional[float] = Field(default=None)
|
||||
energy_wind_turbine_rotor_diameter: Optional[float] = Field(default=None)
|
||||
|
||||
|
|
@ -162,6 +165,7 @@ class EpcPropertyModel(SQLModel, table=True):
|
|||
default=None, sa_column=Column(JSONB, nullable=True)
|
||||
)
|
||||
heating_cylinder_insulation_thickness_mm: Optional[int] = Field(default=None)
|
||||
heating_cylinder_volume_measured_l: Optional[int] = Field(default=None)
|
||||
heating_wwhrs_index_number_1: Optional[int] = Field(default=None)
|
||||
heating_wwhrs_index_number_2: Optional[int] = Field(default=None)
|
||||
heating_shower_outlet_type: Optional[Union[int, str]] = Field(
|
||||
|
|
@ -193,6 +197,7 @@ class EpcPropertyModel(SQLModel, table=True):
|
|||
ventilation_suspended_timber_floor_sealed: Optional[bool] = Field(default=None)
|
||||
ventilation_has_draught_lobby: Optional[bool] = Field(default=None)
|
||||
ventilation_air_permeability_ap4_m3_h_m2: Optional[float] = Field(default=None)
|
||||
ventilation_air_permeability_ap50_m3_h_m2: Optional[float] = Field(default=None)
|
||||
ventilation_mechanical_ventilation_kind: Optional[str] = Field(default=None)
|
||||
mechanical_ventilation: Optional[int] = Field(default=None)
|
||||
mechanical_vent_duct_type: Optional[int] = Field(default=None)
|
||||
|
|
@ -341,6 +346,7 @@ class EpcPropertyModel(SQLModel, table=True):
|
|||
pv.none_or_no_details.percent_roof_area if pv else None
|
||||
),
|
||||
energy_pv_battery_capacity=pvb.pv_battery.battery_capacity if pvb else None,
|
||||
energy_pv_diverter_present=es.pv_diverter_present,
|
||||
energy_wind_turbine_hub_height=wt.hub_height if wt else None,
|
||||
energy_wind_turbine_rotor_diameter=wt.rotor_diameter if wt else None,
|
||||
heating_cylinder_size=h.cylinder_size,
|
||||
|
|
@ -352,6 +358,7 @@ class EpcPropertyModel(SQLModel, table=True):
|
|||
heating_secondary_fuel_type=h.secondary_fuel_type,
|
||||
heating_secondary_heating_type=h.secondary_heating_type,
|
||||
heating_cylinder_insulation_thickness_mm=h.cylinder_insulation_thickness_mm,
|
||||
heating_cylinder_volume_measured_l=h.cylinder_volume_measured_l,
|
||||
heating_wwhrs_index_number_1=h.instantaneous_wwhrs.wwhrs_index_number1,
|
||||
heating_wwhrs_index_number_2=h.instantaneous_wwhrs.wwhrs_index_number2,
|
||||
heating_shower_outlet_type=shower.shower_outlet_type if shower else None,
|
||||
|
|
@ -385,6 +392,9 @@ class EpcPropertyModel(SQLModel, table=True):
|
|||
ventilation_air_permeability_ap4_m3_h_m2=(
|
||||
v.air_permeability_ap4_m3_h_m2 if v else None
|
||||
),
|
||||
ventilation_air_permeability_ap50_m3_h_m2=(
|
||||
v.air_permeability_ap50_m3_h_m2 if v else None
|
||||
),
|
||||
ventilation_mechanical_ventilation_kind=(
|
||||
v.mechanical_ventilation_kind if v else None
|
||||
),
|
||||
|
|
@ -544,6 +554,10 @@ class EpcMainHeatingDetailModel(SQLModel, table=True):
|
|||
main_heating_data_source: Optional[int] = Field(default=None)
|
||||
condensing: Optional[bool] = Field(default=None)
|
||||
weather_compensator: Optional[bool] = Field(default=None)
|
||||
# Community-heating fields (SAP Table 4d; the calculator reads them via
|
||||
# cert_to_inputs for a community-heated dwelling).
|
||||
community_heating_boiler_fuel_type: Optional[int] = Field(default=None)
|
||||
community_heating_chp_fraction: Optional[float] = Field(default=None)
|
||||
|
||||
@classmethod
|
||||
def from_domain(
|
||||
|
|
@ -569,6 +583,8 @@ class EpcMainHeatingDetailModel(SQLModel, table=True):
|
|||
main_heating_data_source=detail.main_heating_data_source,
|
||||
condensing=detail.condensing,
|
||||
weather_compensator=detail.weather_compensator,
|
||||
community_heating_boiler_fuel_type=detail.community_heating_boiler_fuel_type,
|
||||
community_heating_chp_fraction=detail.community_heating_chp_fraction,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -601,6 +617,11 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
wall_insulation_thickness: Optional[Union[str, int]] = Field(
|
||||
default=None, sa_column=Column(JSONB, nullable=True)
|
||||
)
|
||||
# Union[int, str] SAP code (int from the API, str from Site Notes) — JSONB to
|
||||
# preserve the type on round-trip, like the insulation-thickness siblings.
|
||||
wall_insulation_thermal_conductivity: Optional[Union[int, str]] = Field(
|
||||
default=None, sa_column=Column(JSONB, nullable=True)
|
||||
)
|
||||
floor_heat_loss: Optional[int] = Field(default=None)
|
||||
floor_insulation_thickness: Optional[str] = Field(default=None)
|
||||
flat_roof_insulation_thickness: Optional[Union[str, int]] = Field(
|
||||
|
|
@ -627,12 +648,14 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
alt_wall_1_insulation_type: Optional[int] = Field(default=None)
|
||||
alt_wall_1_thickness_measured: Optional[str] = Field(default=None)
|
||||
alt_wall_1_insulation_thickness: Optional[str] = Field(default=None)
|
||||
alt_wall_1_is_sheltered: Optional[bool] = Field(default=None)
|
||||
alt_wall_2_area: Optional[float] = Field(default=None)
|
||||
alt_wall_2_dry_lined: Optional[str] = Field(default=None)
|
||||
alt_wall_2_construction: Optional[int] = Field(default=None)
|
||||
alt_wall_2_insulation_type: Optional[int] = Field(default=None)
|
||||
alt_wall_2_thickness_measured: Optional[str] = Field(default=None)
|
||||
alt_wall_2_insulation_thickness: Optional[str] = Field(default=None)
|
||||
alt_wall_2_is_sheltered: Optional[bool] = Field(default=None)
|
||||
|
||||
@classmethod
|
||||
def from_domain(
|
||||
|
|
@ -653,6 +676,7 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
wall_dry_lined=part.wall_dry_lined,
|
||||
wall_thickness_mm=part.wall_thickness_mm,
|
||||
wall_insulation_thickness=part.wall_insulation_thickness,
|
||||
wall_insulation_thermal_conductivity=part.wall_insulation_thermal_conductivity,
|
||||
floor_heat_loss=part.floor_heat_loss,
|
||||
floor_insulation_thickness=part.floor_insulation_thickness,
|
||||
flat_roof_insulation_thickness=part.flat_roof_insulation_thickness,
|
||||
|
|
@ -677,6 +701,7 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
alt_wall_1_insulation_thickness=(
|
||||
aw1.wall_insulation_thickness if aw1 else None
|
||||
),
|
||||
alt_wall_1_is_sheltered=aw1.is_sheltered if aw1 else None,
|
||||
alt_wall_2_area=aw2.wall_area if aw2 else None,
|
||||
alt_wall_2_dry_lined=aw2.wall_dry_lined if aw2 else None,
|
||||
alt_wall_2_construction=aw2.wall_construction if aw2 else None,
|
||||
|
|
@ -685,6 +710,7 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
alt_wall_2_insulation_thickness=(
|
||||
aw2.wall_insulation_thickness if aw2 else None
|
||||
),
|
||||
alt_wall_2_is_sheltered=aw2.is_sheltered if aw2 else None,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -691,6 +691,7 @@ class EpcPostgresRepository(EpcRepository):
|
|||
secondary_fuel_type=p.heating_secondary_fuel_type,
|
||||
secondary_heating_type=p.heating_secondary_heating_type,
|
||||
cylinder_insulation_thickness_mm=p.heating_cylinder_insulation_thickness_mm,
|
||||
cylinder_volume_measured_l=p.heating_cylinder_volume_measured_l,
|
||||
number_baths=p.heating_number_baths,
|
||||
number_baths_wwhrs=p.heating_number_baths_wwhrs,
|
||||
electric_shower_count=p.heating_electric_shower_count,
|
||||
|
|
@ -718,6 +719,8 @@ class EpcPostgresRepository(EpcRepository):
|
|||
main_heating_data_source=m.main_heating_data_source,
|
||||
condensing=m.condensing,
|
||||
weather_compensator=m.weather_compensator,
|
||||
community_heating_boiler_fuel_type=m.community_heating_boiler_fuel_type,
|
||||
community_heating_chp_fraction=m.community_heating_chp_fraction,
|
||||
)
|
||||
|
||||
@private
|
||||
|
|
@ -768,6 +771,7 @@ class EpcPostgresRepository(EpcRepository):
|
|||
wall_dry_lined=bp.wall_dry_lined,
|
||||
wall_thickness_mm=bp.wall_thickness_mm,
|
||||
wall_insulation_thickness=bp.wall_insulation_thickness,
|
||||
wall_insulation_thermal_conductivity=bp.wall_insulation_thermal_conductivity,
|
||||
sap_alternative_wall_1=self._to_alt_wall(bp, 1),
|
||||
sap_alternative_wall_2=self._to_alt_wall(bp, 2),
|
||||
floor_heat_loss=bp.floor_heat_loss,
|
||||
|
|
@ -819,6 +823,7 @@ class EpcPostgresRepository(EpcRepository):
|
|||
if n == 1
|
||||
else bp.alt_wall_2_insulation_thickness
|
||||
)
|
||||
sheltered = bp.alt_wall_1_is_sheltered if n == 1 else bp.alt_wall_2_is_sheltered
|
||||
return SapAlternativeWall(
|
||||
wall_area=area,
|
||||
wall_dry_lined=_require(dry_lined, f"alt_wall_{n}_dry_lined"),
|
||||
|
|
@ -830,6 +835,9 @@ class EpcPostgresRepository(EpcRepository):
|
|||
thickness_measured, f"alt_wall_{n}_thickness_measured"
|
||||
),
|
||||
wall_insulation_thickness=insulation_thickness,
|
||||
# Nullable column (added later than the alt wall itself); a row from
|
||||
# before the column existed reads None → the domain default False.
|
||||
is_sheltered=sheltered if sheltered is not None else False,
|
||||
)
|
||||
|
||||
@private
|
||||
|
|
@ -864,6 +872,7 @@ class EpcPostgresRepository(EpcRepository):
|
|||
if pv_array_rows
|
||||
else None
|
||||
),
|
||||
pv_diverter_present=p.energy_pv_diverter_present,
|
||||
mains_gas=p.energy_mains_gas,
|
||||
meter_type=p.energy_meter_type,
|
||||
pv_battery_count=p.energy_pv_battery_count,
|
||||
|
|
@ -949,6 +958,7 @@ class EpcPostgresRepository(EpcRepository):
|
|||
suspended_timber_floor_sealed=p.ventilation_suspended_timber_floor_sealed,
|
||||
has_draught_lobby=p.ventilation_has_draught_lobby,
|
||||
air_permeability_ap4_m3_h_m2=p.ventilation_air_permeability_ap4_m3_h_m2,
|
||||
air_permeability_ap50_m3_h_m2=p.ventilation_air_permeability_ap50_m3_h_m2,
|
||||
mechanical_ventilation_kind=p.ventilation_mechanical_ventilation_kind,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue