mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Synthesise windows, lighting, ventilation and hot water for 17.1 certs 🟩
Rich certs (14/1000) use lodged window_area; the windowless majority synthesise glazing from the glazed_area band via _synthesise_17_1_sap_windows (own seam, inherited 20.0.0 coefficients); lighting/ventilation/hot-water mirror 18.0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8895b41d4
commit
0f3321e655
1 changed files with 109 additions and 5 deletions
|
|
@ -603,11 +603,18 @@ class EpcPropertyDataMapper:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_rdsap_schema_17_1(schema: RdSapSchema17_1) -> EpcPropertyData:
|
def from_rdsap_schema_17_1(schema: RdSapSchema17_1) -> EpcPropertyData:
|
||||||
es = schema.sap_energy_source
|
es = schema.sap_energy_source
|
||||||
|
# ADR-0028: instantaneous_wwhrs holds bath/shower ROOM counts.
|
||||||
|
iw = schema.sap_heating.instantaneous_wwhrs
|
||||||
return EpcPropertyData(
|
return EpcPropertyData(
|
||||||
uprn=schema.uprn,
|
uprn=schema.uprn,
|
||||||
assessment_type=schema.assessment_type,
|
assessment_type=schema.assessment_type,
|
||||||
sap_version=schema.sap_version,
|
sap_version=schema.sap_version,
|
||||||
dwelling_type=schema.dwelling_type.value,
|
# ADR-0028: 17.1 lodges dwelling_type as str OR localised dict.
|
||||||
|
dwelling_type=(
|
||||||
|
schema.dwelling_type
|
||||||
|
if isinstance(schema.dwelling_type, str)
|
||||||
|
else schema.dwelling_type.value
|
||||||
|
),
|
||||||
property_type=str(schema.property_type),
|
property_type=str(schema.property_type),
|
||||||
built_form=str(schema.built_form),
|
built_form=str(schema.built_form),
|
||||||
address_line_1=schema.address_line_1,
|
address_line_1=schema.address_line_1,
|
||||||
|
|
@ -631,12 +638,23 @@ class EpcPropertyDataMapper:
|
||||||
heated_rooms_count=schema.heated_room_count,
|
heated_rooms_count=schema.heated_room_count,
|
||||||
wet_rooms_count=0,
|
wet_rooms_count=0,
|
||||||
extensions_count=schema.extensions_count,
|
extensions_count=schema.extensions_count,
|
||||||
open_chimneys_count=0,
|
open_chimneys_count=schema.open_fireplaces_count,
|
||||||
insulated_door_count=schema.insulated_door_count,
|
insulated_door_count=schema.insulated_door_count,
|
||||||
draughtproofed_door_count=None,
|
draughtproofed_door_count=None,
|
||||||
|
percent_draughtproofed=schema.percent_draughtproofed,
|
||||||
|
# ADR-0028: sheltered_sides from built_form, else the calculator
|
||||||
|
# assumes mid-terrace (2) for every dwelling.
|
||||||
|
sap_ventilation=SapVentilation(
|
||||||
|
sheltered_sides=_api_sheltered_sides(schema.built_form),
|
||||||
|
),
|
||||||
|
# ADR-0028: total + low-energy OUTLET counts, not a bulb split.
|
||||||
led_fixed_lighting_bulbs_count=0,
|
led_fixed_lighting_bulbs_count=0,
|
||||||
cfl_fixed_lighting_bulbs_count=0,
|
cfl_fixed_lighting_bulbs_count=0,
|
||||||
incandescent_fixed_lighting_bulbs_count=0,
|
incandescent_fixed_lighting_bulbs_count=(
|
||||||
|
schema.fixed_lighting_outlets_count
|
||||||
|
- schema.low_energy_fixed_lighting_outlets_count
|
||||||
|
),
|
||||||
|
low_energy_fixed_lighting_bulbs_count=schema.low_energy_fixed_lighting_outlets_count,
|
||||||
roofs=EpcPropertyDataMapper._map_energy_elements(schema.roofs),
|
roofs=EpcPropertyDataMapper._map_energy_elements(schema.roofs),
|
||||||
walls=EpcPropertyDataMapper._map_energy_elements(schema.walls),
|
walls=EpcPropertyDataMapper._map_energy_elements(schema.walls),
|
||||||
floors=EpcPropertyDataMapper._map_energy_elements(schema.floors),
|
floors=EpcPropertyDataMapper._map_energy_elements(schema.floors),
|
||||||
|
|
@ -651,6 +669,19 @@ class EpcPropertyDataMapper:
|
||||||
),
|
),
|
||||||
sap_heating=SapHeating(
|
sap_heating=SapHeating(
|
||||||
instantaneous_wwhrs=InstantaneousWwhrs(),
|
instantaneous_wwhrs=InstantaneousWwhrs(),
|
||||||
|
# ADR-0028: derive HW demand counts from bath/shower ROOM counts.
|
||||||
|
number_baths=(
|
||||||
|
iw.rooms_with_bath_and_or_shower
|
||||||
|
+ iw.rooms_with_bath_and_mixer_shower
|
||||||
|
if iw is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
mixer_shower_count=(
|
||||||
|
iw.rooms_with_mixer_shower_no_bath
|
||||||
|
+ iw.rooms_with_bath_and_mixer_shower
|
||||||
|
if iw is not None
|
||||||
|
else None
|
||||||
|
),
|
||||||
main_heating_details=[
|
main_heating_details=[
|
||||||
MainHeatingDetail(
|
MainHeatingDetail(
|
||||||
has_fghrs=d.has_fghrs == "Y",
|
has_fghrs=d.has_fghrs == "Y",
|
||||||
|
|
@ -685,7 +716,27 @@ class EpcPropertyDataMapper:
|
||||||
secondary_heating_type=schema.sap_heating.secondary_heating_type,
|
secondary_heating_type=schema.sap_heating.secondary_heating_type,
|
||||||
cylinder_insulation_thickness_mm=schema.sap_heating.cylinder_insulation_thickness,
|
cylinder_insulation_thickness_mm=schema.sap_heating.cylinder_insulation_thickness,
|
||||||
),
|
),
|
||||||
sap_windows=[],
|
sap_windows=(
|
||||||
|
[
|
||||||
|
SapWindow(
|
||||||
|
frame_material=None,
|
||||||
|
glazing_gap=0,
|
||||||
|
orientation=w.orientation,
|
||||||
|
window_type=w.window_type,
|
||||||
|
glazing_type=w.glazing_type,
|
||||||
|
# ADR-0028: 14 rich certs lodge real per-window area.
|
||||||
|
window_width=_measurement_value(w.window_area),
|
||||||
|
window_height=1.0,
|
||||||
|
draught_proofed=False,
|
||||||
|
window_location=w.window_location,
|
||||||
|
window_wall_type=0,
|
||||||
|
permanent_shutters_present=False,
|
||||||
|
)
|
||||||
|
for w in schema.sap_windows
|
||||||
|
]
|
||||||
|
if schema.sap_windows
|
||||||
|
else _synthesise_17_1_sap_windows(schema)
|
||||||
|
),
|
||||||
sap_energy_source=SapEnergySource(
|
sap_energy_source=SapEnergySource(
|
||||||
mains_gas=es.mains_gas == "Y",
|
mains_gas=es.mains_gas == "Y",
|
||||||
meter_type=str(es.meter_type),
|
meter_type=str(es.meter_type),
|
||||||
|
|
@ -701,7 +752,7 @@ class EpcPropertyDataMapper:
|
||||||
percent_roof_area=es.photovoltaic_supply.none_or_no_details.percent_roof_area,
|
percent_roof_area=es.photovoltaic_supply.none_or_no_details.percent_roof_area,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if es.photovoltaic_supply
|
if getattr(es.photovoltaic_supply, "none_or_no_details", None)
|
||||||
else None
|
else None
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -2130,6 +2181,14 @@ class EpcPropertyDataMapper:
|
||||||
from_dict(RdSapSchema18_0, data)
|
from_dict(RdSapSchema18_0, data)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if schema == "RdSAP-Schema-17.1":
|
||||||
|
from datatypes.epc.schema.rdsap_schema_17_1 import RdSapSchema17_1
|
||||||
|
|
||||||
|
return _clear_basement_flag_when_system_built(
|
||||||
|
EpcPropertyDataMapper.from_rdsap_schema_17_1(
|
||||||
|
from_dict(RdSapSchema17_1, data)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
raise ValueError(f"Unsupported EPC schema: {schema!r}")
|
raise ValueError(f"Unsupported EPC schema: {schema!r}")
|
||||||
|
|
||||||
|
|
@ -3087,6 +3146,51 @@ def _synthesise_18_0_sap_windows(schema: RdSapSchema18_0) -> List[SapWindow]:
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# ADR-0028: multiple_glazing_type "ND" (Not Defined, 56/1000 17.1 certs) → the
|
||||||
|
# DG-modal default, as for 18.0.
|
||||||
|
_RDSAP17_1_ND_GLAZING_TYPE: int = 2
|
||||||
|
|
||||||
|
|
||||||
|
def _synthesise_17_1_sap_windows(schema: RdSapSchema17_1) -> List[SapWindow]:
|
||||||
|
"""ADR-0028 Reduced-Field Synthesis of `sap_windows` for a 17.1 cert.
|
||||||
|
|
||||||
|
A separate seam from the 18.0/20.0.0 helpers so 17.1 can diverge, but it
|
||||||
|
reuses the inherited 20.0.0 coefficients unchanged (ADR-0028: 969/1000 band-1
|
||||||
|
with no measured band-1 windows; its band-4 rich certs reproduce the model).
|
||||||
|
990/1000 certs carry no per-window array — synthesise total glazing as
|
||||||
|
`ratio x TFA`, split 4-way across N/E/S/W with height=1.0.
|
||||||
|
"""
|
||||||
|
band_multiplier = _RDSAP20_GLAZED_AREA_BAND_MULTIPLIER.get(schema.glazed_area, 1.0)
|
||||||
|
total_area = (
|
||||||
|
_RDSAP20_GLAZING_RATIO * float(schema.total_floor_area) * band_multiplier
|
||||||
|
)
|
||||||
|
per_window_area = total_area / len(_RDSAP20_SYNTH_ORIENTATIONS)
|
||||||
|
# ADR-0028: 17.1 glazed_type codes 1-8 are identical to 20.0.0's (verified
|
||||||
|
# against epc_codes.csv); the "ND" string falls back to the DG-modal default.
|
||||||
|
mgt = schema.multiple_glazing_type
|
||||||
|
glazing_type = (
|
||||||
|
_api_cascade_glazing_type(mgt)
|
||||||
|
if isinstance(mgt, int)
|
||||||
|
else _RDSAP17_1_ND_GLAZING_TYPE
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
SapWindow(
|
||||||
|
frame_material=None,
|
||||||
|
glazing_gap=0,
|
||||||
|
orientation=orientation,
|
||||||
|
window_type=0,
|
||||||
|
glazing_type=glazing_type,
|
||||||
|
window_width=per_window_area,
|
||||||
|
window_height=1.0,
|
||||||
|
draught_proofed=False,
|
||||||
|
window_location=0,
|
||||||
|
window_wall_type=0,
|
||||||
|
permanent_shutters_present=False,
|
||||||
|
)
|
||||||
|
for orientation in _RDSAP20_SYNTH_ORIENTATIONS
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# GOV.UK API `glazing_type` integer → (u_value W/m²K, g_perpendicular,
|
# GOV.UK API `glazing_type` integer → (u_value W/m²K, g_perpendicular,
|
||||||
# frame_factor) lookup the cascade reads via `window_transmission_
|
# frame_factor) lookup the cascade reads via `window_transmission_
|
||||||
# details` for per-window cascade fidelity. The cascade defaults to a
|
# details` for per-window cascade fidelity. The cascade defaults to a
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue