mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Map full-SAP energy source, mains-gas inference and lighting bulbs 🟩
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
cb4d080da2
commit
acd0ed485d
3 changed files with 66 additions and 5 deletions
|
|
@ -688,9 +688,17 @@ class EpcPropertyDataMapper:
|
|||
door_count=door_count,
|
||||
insulated_door_count=door_count,
|
||||
insulated_door_u_value=insulated_door_u,
|
||||
# D5: lighting outlet counts → bulb counts (full SAP lodges total +
|
||||
# low-energy outlet counts, like the RdSAP 17.1 path).
|
||||
cfl_fixed_lighting_bulbs_count=0,
|
||||
led_fixed_lighting_bulbs_count=0,
|
||||
incandescent_fixed_lighting_bulbs_count=0,
|
||||
incandescent_fixed_lighting_bulbs_count=(
|
||||
(schema.sap_energy_source.fixed_lighting_outlets_count or 0)
|
||||
- (schema.sap_energy_source.low_energy_fixed_lighting_outlets_count or 0)
|
||||
),
|
||||
low_energy_fixed_lighting_bulbs_count=(
|
||||
schema.sap_energy_source.low_energy_fixed_lighting_outlets_count or 0
|
||||
),
|
||||
# D4: full SAP lodges the measured U as text in the element
|
||||
# description ("Average thermal transmittance X W/m²K"); carry it
|
||||
# through so u_wall/u_floor/u_roof parse it instead of re-deriving
|
||||
|
|
@ -714,14 +722,18 @@ class EpcPropertyDataMapper:
|
|||
# the domain SapHeating the calculator consumes (PCDB efficiency via
|
||||
# main_heating_index_number; water cascade via water_heating_code).
|
||||
sap_heating=_sap_17_1_heating(schema),
|
||||
# D5: mains_gas derived from the heating fuel (full SAP has no
|
||||
# explicit flag); tariff → meter_type; wind turbines pass through.
|
||||
sap_energy_source=SapEnergySource(
|
||||
mains_gas=False,
|
||||
meter_type="",
|
||||
mains_gas=_sap_dwelling_on_mains_gas(schema),
|
||||
meter_type=str(schema.sap_energy_source.electricity_tariff or ""),
|
||||
pv_battery_count=0,
|
||||
wind_turbines_count=0,
|
||||
wind_turbines_count=schema.sap_energy_source.wind_turbines_count or 0,
|
||||
gas_smart_meter_present=False,
|
||||
is_dwelling_export_capable=False,
|
||||
wind_turbines_terrain_type="",
|
||||
wind_turbines_terrain_type=str(
|
||||
schema.sap_energy_source.wind_turbine_terrain_type or ""
|
||||
),
|
||||
electricity_smart_meter_present=False,
|
||||
),
|
||||
)
|
||||
|
|
@ -2504,6 +2516,19 @@ _SAP_LIVING_AREA_FRACTION_BY_ROOMS: Final[Dict[int, float]] = {
|
|||
}
|
||||
|
||||
|
||||
# SAP main_fuel_type code for mains gas.
|
||||
_SAP_MAINS_GAS_FUEL_CODE: Final[int] = 1
|
||||
|
||||
|
||||
def _sap_dwelling_on_mains_gas(schema: SapSchema17_1) -> bool:
|
||||
"""D5: full SAP has no explicit mains-gas flag — infer it from whether any
|
||||
main-heating system burns mains gas (fuel code 1)."""
|
||||
return any(
|
||||
d.main_fuel_type == _SAP_MAINS_GAS_FUEL_CODE
|
||||
for d in schema.sap_heating.main_heating_details
|
||||
)
|
||||
|
||||
|
||||
def _sap_17_1_heating(schema: SapSchema17_1) -> SapHeating:
|
||||
"""D6: map full-SAP `sap_heating` onto the domain `SapHeating`. Field names
|
||||
differ from RdSAP — `is_flue_fan_present`→`fan_flue_present`,
|
||||
|
|
|
|||
|
|
@ -232,6 +232,28 @@ class TestFromSapSchema17_1LivingArea:
|
|||
assert self._map("sap_17_1_flat.json").habitable_rooms_count == 3
|
||||
|
||||
|
||||
class TestFromSapSchema17_1EnergySource:
|
||||
"""Slice D5: full-SAP sap_energy_source → mains_gas (derived from heating
|
||||
fuel), lighting outlet counts → bulb counts, and the tariff/meter."""
|
||||
|
||||
@pytest.fixture
|
||||
def sample(self) -> EpcPropertyData:
|
||||
schema = from_dict(SapSchema17_1, load("sap_17_1.json"))
|
||||
return EpcPropertyDataMapper.from_sap_schema_17_1(schema)
|
||||
|
||||
def test_mains_gas_derived_from_heating_fuel(self, sample: EpcPropertyData) -> None:
|
||||
# Main heating fuel is mains gas (code 1) → mains_gas True.
|
||||
assert sample.sap_energy_source.mains_gas is True
|
||||
|
||||
def test_lighting_outlet_counts_to_bulbs(self, sample: EpcPropertyData) -> None:
|
||||
# 1 fixed outlet, 1 low-energy → 1 low-energy bulb, 0 incandescent.
|
||||
assert sample.low_energy_fixed_lighting_bulbs_count == 1
|
||||
assert sample.incandescent_fixed_lighting_bulbs_count == 0
|
||||
|
||||
def test_wind_turbine_fields_pass_through(self, sample: EpcPropertyData) -> None:
|
||||
assert sample.sap_energy_source.wind_turbines_count == 0
|
||||
|
||||
|
||||
class TestFromSapSchema17_1Heating:
|
||||
"""Slice D6: full-SAP sap_heating (differing field names) maps onto the
|
||||
domain SapHeating + MainHeatingDetail the calculator consumes."""
|
||||
|
|
|
|||
|
|
@ -88,6 +88,19 @@ class SapBuildingPart:
|
|||
building_part_number: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SapEnergySource:
|
||||
"""Electricity tariff, on-site generation and lighting. Lighting outlet
|
||||
counts map to the engine's bulb counts; `mains_gas` is derived from the
|
||||
heating fuel (full SAP has no explicit flag)."""
|
||||
|
||||
electricity_tariff: Optional[int] = None
|
||||
wind_turbines_count: Optional[int] = None
|
||||
wind_turbine_terrain_type: Optional[int] = None
|
||||
fixed_lighting_outlets_count: Optional[int] = None
|
||||
low_energy_fixed_lighting_outlets_count: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SapMainHeatingDetail:
|
||||
"""One main-heating system. Field names differ from RdSAP (e.g.
|
||||
|
|
@ -157,6 +170,7 @@ class SapSchema17_1:
|
|||
sap_opening_types: List[SapOpeningType]
|
||||
sap_building_parts: List[SapBuildingPart]
|
||||
sap_heating: SapHeating
|
||||
sap_energy_source: SapEnergySource = field(default_factory=SapEnergySource)
|
||||
# measured living-room area (m²); the engine consumes it via a back-solved
|
||||
# habitable_rooms_count (Table 27). Optional — 100% present in the corpus.
|
||||
living_area: Optional[Union[int, float]] = None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue