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,
|
door_count=door_count,
|
||||||
insulated_door_count=door_count,
|
insulated_door_count=door_count,
|
||||||
insulated_door_u_value=insulated_door_u,
|
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,
|
cfl_fixed_lighting_bulbs_count=0,
|
||||||
led_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
|
# D4: full SAP lodges the measured U as text in the element
|
||||||
# description ("Average thermal transmittance X W/m²K"); carry it
|
# description ("Average thermal transmittance X W/m²K"); carry it
|
||||||
# through so u_wall/u_floor/u_roof parse it instead of re-deriving
|
# 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
|
# the domain SapHeating the calculator consumes (PCDB efficiency via
|
||||||
# main_heating_index_number; water cascade via water_heating_code).
|
# main_heating_index_number; water cascade via water_heating_code).
|
||||||
sap_heating=_sap_17_1_heating(schema),
|
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(
|
sap_energy_source=SapEnergySource(
|
||||||
mains_gas=False,
|
mains_gas=_sap_dwelling_on_mains_gas(schema),
|
||||||
meter_type="",
|
meter_type=str(schema.sap_energy_source.electricity_tariff or ""),
|
||||||
pv_battery_count=0,
|
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,
|
gas_smart_meter_present=False,
|
||||||
is_dwelling_export_capable=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,
|
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:
|
def _sap_17_1_heating(schema: SapSchema17_1) -> SapHeating:
|
||||||
"""D6: map full-SAP `sap_heating` onto the domain `SapHeating`. Field names
|
"""D6: map full-SAP `sap_heating` onto the domain `SapHeating`. Field names
|
||||||
differ from RdSAP — `is_flue_fan_present`→`fan_flue_present`,
|
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
|
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:
|
class TestFromSapSchema17_1Heating:
|
||||||
"""Slice D6: full-SAP sap_heating (differing field names) maps onto the
|
"""Slice D6: full-SAP sap_heating (differing field names) maps onto the
|
||||||
domain SapHeating + MainHeatingDetail the calculator consumes."""
|
domain SapHeating + MainHeatingDetail the calculator consumes."""
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,19 @@ class SapBuildingPart:
|
||||||
building_part_number: Optional[int] = None
|
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
|
@dataclass
|
||||||
class SapMainHeatingDetail:
|
class SapMainHeatingDetail:
|
||||||
"""One main-heating system. Field names differ from RdSAP (e.g.
|
"""One main-heating system. Field names differ from RdSAP (e.g.
|
||||||
|
|
@ -157,6 +170,7 @@ class SapSchema17_1:
|
||||||
sap_opening_types: List[SapOpeningType]
|
sap_opening_types: List[SapOpeningType]
|
||||||
sap_building_parts: List[SapBuildingPart]
|
sap_building_parts: List[SapBuildingPart]
|
||||||
sap_heating: SapHeating
|
sap_heating: SapHeating
|
||||||
|
sap_energy_source: SapEnergySource = field(default_factory=SapEnergySource)
|
||||||
# measured living-room area (m²); the engine consumes it via a back-solved
|
# measured living-room area (m²); the engine consumes it via a back-solved
|
||||||
# habitable_rooms_count (Table 27). Optional — 100% present in the corpus.
|
# habitable_rooms_count (Table 27). Optional — 100% present in the corpus.
|
||||||
living_area: Optional[Union[int, float]] = None
|
living_area: Optional[Union[int, float]] = None
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue