diff --git a/backend/documents_parser/tests/fixtures/Summary_001431_lpg_boiler.pdf b/backend/documents_parser/tests/fixtures/Summary_001431_lpg_boiler.pdf new file mode 100644 index 00000000..2b7a7287 Binary files /dev/null and b/backend/documents_parser/tests/fixtures/Summary_001431_lpg_boiler.pdf differ diff --git a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py index c05f1437..b376f876 100644 --- a/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py +++ b/backend/documents_parser/tests/test_summary_pdf_mapper_chain.py @@ -84,6 +84,7 @@ _SUMMARY_000890_PDF = _FIXTURES / "Summary_000890.pdf" # cert 7800 (two electri _SUMMARY_000565_PDF = _FIXTURES / "Summary_000565.pdf" # cert 000565 (5-bp Elmhurst-only) _SUMMARY_001431_CASE20_PDF = _FIXTURES / "Summary_001431_case20.pdf" # sim case 20 (storage heaters + RR type-2 + wrapped "Double between 2002 and 2021" glazing) _SUMMARY_001431_TOPFLOOR_PDF = _FIXTURES / "Summary_001431_topfloor_flat.pdf" # gas-boiler-upgrade recommendation "after" — top-floor flat, PS sloping roof; exercises the Date-Built age-band + flat-position layout regressions +_SUMMARY_001431_LPG_PDF = _FIXTURES / "Summary_001431_lpg_boiler.pdf" # lpg-boiler recommendation "before" — §14 SAP code 115, §15 "Bottled gas"; exercises the bottled-LPG main-fuel mapping # GOV.UK EPB API JSON for cert 001479 — the API-path counterpart of the # Summary_001479.pdf fixture. Together they drive the API ≡ Summary @@ -180,6 +181,27 @@ def test_summary_001431_topfloor_extracts_main_property_age_band() -> None: assert survey.construction_age_band == "C 1930-1949" +def test_summary_001431_lpg_boiler_maps_main_fuel_to_bottled_lpg() -> None: + # Arrange — the lpg-boiler recommendation "before" Summary lodges + # §14.0 SAP code 115 (a Table 4b gas-family boiler row), §15.0 + # "Water Heating Fuel Type: Bottled gas", and §14.2 "Main gas: Yes". + # The boiler burns bottled LPG, not mains gas; the mapper must + # resolve the carrier from the "Bottled gas" label, NOT default to + # mains gas via the (contradictory) meter flag. Table-route code 3 = + # bottled LPG main heating (Table 32/12 10.30/9.46 p/kWh) — NOT code + # 5, which collides with anthracite (`canonical_fuel_code`). + pages = _summary_pdf_to_textract_style_pages(_SUMMARY_001431_LPG_PDF) + site_notes = ElmhurstSiteNotesExtractor(pages).extract() + + # Act + epc = EpcPropertyDataMapper.from_elmhurst_site_notes(site_notes) + + # Assert + main = epc.sap_heating.main_heating_details[0] + assert main.main_fuel_type == 3 + assert epc.sap_heating.water_heating_fuel == 3 + + def test_summary_001431_topfloor_flat_classified_as_top_floor() -> None: # Arrange — the recommendation "after" Summary lodges §6.0 "Position # of flat in block of flats: Top Floor": floor "A Another dwelling diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 01512ddf..32af358d 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -4537,6 +4537,17 @@ _ELMHURST_MAIN_FUEL_TO_SAP10: Dict[str, int] = { # existing oddity as "Oil" → 8; both labels are unused by any live # fixture). Live form on Elmhurst worksheets is "Bulk LPG". "Bulk LPG": 27, + # Elmhurst Summary §14.0 / §15.0 lodging form for BOTTLED LPG + # (cylinders) — the recommendation worksheets lodge "Bottled gas" as + # the §15.0 "Water Heating Fuel Type" for an SAP-code-115 boiler. + # 3 = API/epc-codes `main_fuel` code for bottled LPG main heating, + # which routes via `API_FUEL_TO_TABLE_32`/`API_FUEL_TO_TABLE_12` → + # Table-code 3 (bottled LPG main heating, 10.30 / 9.46 p/kWh). NOT + # the legacy "LPG bottled": 5 above — API code 5 = anthracite, and + # `canonical_fuel_code` resolves the same-valued Table-32 code 5 to + # anthracite (3.64 p/kWh), so a 5 here would mis-price the dwelling + # as cheap solid fuel (the cohort-2100 -61-SAP collision class). + "Bottled gas": 3, # Elmhurst Summary §15.0 "Water Heating Fuel Type" labels for the # bio-liquid fuels added to the EES dict above. Values are Table 32 # codes verbatim (no API enum collision). Spec: SAP 10.2 Table 12 @@ -4873,7 +4884,12 @@ _GAS_BOILER_SAP_MAIN_HEATING_CODES: Final[frozenset[int]] = ( # of these so it can't mis-assign electricity from a separate immersion # (where §15.0 lodges the immersion's fuel, not the boiler's) — that # case still strict-raises `MissingMainFuelType` to force a mapper fix. -_GAS_LPG_MAIN_FUEL_CODES: Final[frozenset[int]] = frozenset({1, 5, 6, 7, 26, 27}) +# 3 = bottled LPG main heating ("Bottled gas" label); the other LPG +# carriers are the legacy API LPG codes (5/6/7) + the live "Bulk LPG" +# (27). All count as a gas/LPG carrier so `_elmhurst_gas_boiler_main_fuel` +# adopts a §15.0-lodged bottled-LPG water fuel for the boiler's space- +# heating carrier instead of falling through to the mains-gas meter flag. +_GAS_LPG_MAIN_FUEL_CODES: Final[frozenset[int]] = frozenset({1, 3, 5, 6, 7, 26, 27}) # SAP10 main-fuel code for mains gas (`_ELMHURST_MAIN_FUEL_TO_SAP10` # "Mains gas"). Used when a Table 4b gas boiler's carrier can't be read