mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Merge pull request #1554 from Hestia-Homes/feature/pashub-normalize-main-fuel-1552
Pashub sitenotes mapping: normalise main fuel 1552
This commit is contained in:
commit
2f0d5a25f1
2 changed files with 95 additions and 4 deletions
|
|
@ -6027,7 +6027,7 @@ def _map_sap_heating(
|
|||
main_heating_details=[
|
||||
MainHeatingDetail(
|
||||
has_fghrs=main.flue_gas_heat_recovery_system,
|
||||
main_fuel_type=fuel_type,
|
||||
main_fuel_type=_pashub_main_fuel_code(fuel_type),
|
||||
heat_emitter_type=main.emitter,
|
||||
emitter_temperature=main.emitter_temperature,
|
||||
fan_flue_present=main.fan_assist,
|
||||
|
|
@ -7030,6 +7030,57 @@ def _elmhurst_main_fuel_int(fuel_type: str) -> Optional[int]:
|
|||
return _ELMHURST_MAIN_FUEL_TO_SAP10.get(fuel_type)
|
||||
|
||||
|
||||
class UnmappedPasHubLabel(ValueError):
|
||||
"""A PasHub Site-Notes survey lodged a finite-enum label that the mapper
|
||||
does not yet know how to translate to the SAP10 cascade enum.
|
||||
|
||||
Raised by the strict PasHub label-to-code helpers to surface mapper-
|
||||
coverage gaps at the `from_site_notes` boundary (ADR-0015) instead of
|
||||
passing the raw string downstream, where it resurfaces as the
|
||||
calculator's deep `MissingMainFuelType` raise — or worse, is silently
|
||||
mis-billed. Mirrors `UnmappedApiCode` / `UnmappedElmhurstLabel`.
|
||||
|
||||
Distinguish "lodging absent" (a genuinely blank cell — the helper returns
|
||||
it unchanged, correct) from "lodging present but unrecognised" (raise —
|
||||
a Fuel label the lookup doesn't yet cover, needs a dict entry added).
|
||||
"""
|
||||
|
||||
def __init__(self, field: str, value: str) -> None:
|
||||
super().__init__(
|
||||
f"unmapped PasHub {field} label: {value!r}; "
|
||||
f"add an entry to the corresponding mapper lookup dict"
|
||||
)
|
||||
self.field = field
|
||||
self.value = value
|
||||
|
||||
|
||||
# PasHub Site-Notes surveyed main-heating "Fuel" labels mapped to SAP10 fuel
|
||||
# codes (the epc-codes `main_fuel` enum the calculator's resolver routes via
|
||||
# its API→Table-12/32 translation). Seeded only with labels confirmed from
|
||||
# live PasHub fixtures; a dedicated lookup rather than reuse of the Elmhurst
|
||||
# map, which carries legacy label oddities PasHub must not inherit. New labels
|
||||
# are a one-line addition — until then they strict-raise at the boundary
|
||||
# (ADR-0015) rather than being silently mis-billed downstream.
|
||||
_PASHUB_MAIN_FUEL_TO_SAP10: Dict[str, int] = {
|
||||
"Mains gas": 26, # matches the Elmhurst mapper's mains-gas code
|
||||
"Electricity": 30, # the standard-electricity fuel code
|
||||
}
|
||||
|
||||
|
||||
def _pashub_main_fuel_code(fuel_label: str) -> Union[int, str]:
|
||||
"""Resolve a PasHub surveyed main-heating Fuel label to a SAP10 fuel code
|
||||
at the mapper boundary (ADR-0015). A genuinely blank label is the "no main
|
||||
heating" shape and passes through unchanged; a non-empty label the lookup
|
||||
does not cover strict-raises `UnmappedPasHubLabel` so the gap is fixed here,
|
||||
never silently mis-billed downstream."""
|
||||
if not fuel_label:
|
||||
return fuel_label
|
||||
code = _PASHUB_MAIN_FUEL_TO_SAP10.get(fuel_label)
|
||||
if code is None:
|
||||
raise UnmappedPasHubLabel("main fuel", fuel_label)
|
||||
return code
|
||||
|
||||
|
||||
def _resolve_elmhurst_underfloor_subtype(
|
||||
main_floor: ElmhurstFloorDetails,
|
||||
main_age_band: str,
|
||||
|
|
|
|||
|
|
@ -160,8 +160,9 @@ class TestFromSiteNotesExample1:
|
|||
# --- main heating ---
|
||||
|
||||
def test_main_heating_fuel(self, result: EpcPropertyData) -> None:
|
||||
# heating_and_hot_water.main_heating.fuel: "Mains gas"
|
||||
assert result.sap_heating.main_heating_details[0].main_fuel_type == "Mains gas"
|
||||
# heating_and_hot_water.main_heating.fuel: "Mains gas" is normalized
|
||||
# at the mapper boundary to SAP fuel code 26 (matching Elmhurst).
|
||||
assert result.sap_heating.main_heating_details[0].main_fuel_type == 26
|
||||
|
||||
def test_main_heating_emitter(self, result: EpcPropertyData) -> None:
|
||||
# heating_and_hot_water.main_heating.emitter: "Radiators"
|
||||
|
|
@ -378,7 +379,7 @@ class TestFromSiteNotesExample1:
|
|||
main_heating_details=[
|
||||
MainHeatingDetail(
|
||||
has_fghrs=False,
|
||||
main_fuel_type="Mains gas",
|
||||
main_fuel_type=26,
|
||||
heat_emitter_type="Radiators",
|
||||
emitter_temperature="Unknown",
|
||||
fan_flue_present=True,
|
||||
|
|
@ -819,3 +820,42 @@ class TestElmhurstSecondaryFuelFromSapCode:
|
|||
# Act / Assert
|
||||
with pytest.raises(UnmappedElmhurstLabel):
|
||||
_elmhurst_secondary_fuel_from_sap_code(620)
|
||||
|
||||
|
||||
class TestPasHubUnmappedMainFuel:
|
||||
"""A PasHub survey whose surveyed main-heating Fuel label the mapper does
|
||||
not yet cover must strict-raise `UnmappedPasHubLabel` at the boundary,
|
||||
naming the label — so a coverage gap is fixed here (a one-line lookup
|
||||
addition) rather than passed downstream as a raw string to resurface as
|
||||
the calculator's `MissingMainFuelType`, or silently mis-billed.
|
||||
"""
|
||||
|
||||
def test_unrecognised_fuel_label_raises_naming_the_label(self) -> None:
|
||||
# Arrange — an example fixture whose Fuel cell carries a label the
|
||||
# PasHub lookup does not yet know.
|
||||
from datatypes.epc.domain.mapper import UnmappedPasHubLabel
|
||||
|
||||
raw = load("pashub_rdsap_site_notes_example1.json")
|
||||
raw["heating_and_hot_water"]["main_heating"]["fuel"] = "Anthracite"
|
||||
survey = from_dict(PasHubRdSapSiteNotes, raw)
|
||||
|
||||
# Act / Assert
|
||||
with pytest.raises(UnmappedPasHubLabel, match="Anthracite"):
|
||||
EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
def test_blank_fuel_electric_system_resolves_to_electricity_code(self) -> None:
|
||||
# Arrange — an all-electric system whose surveyed Fuel cell is blank;
|
||||
# the existing electric-system inference must survive normalization and
|
||||
# emit the standard-electricity SAP fuel code (30), not the label.
|
||||
raw = load("pashub_rdsap_site_notes_example1.json")
|
||||
raw["heating_and_hot_water"]["main_heating"]["fuel"] = ""
|
||||
raw["heating_and_hot_water"]["main_heating"][
|
||||
"system_type"
|
||||
] = "Electric storage heaters"
|
||||
survey = from_dict(PasHubRdSapSiteNotes, raw)
|
||||
|
||||
# Act
|
||||
result = EpcPropertyDataMapper.from_site_notes(survey)
|
||||
|
||||
# Assert
|
||||
assert result.sap_heating.main_heating_details[0].main_fuel_type == 30
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue