"""The dwelling interpretation that feeds `Products.ashp_bundle_cost` — reading an `EpcPropertyData` into a typed `AshpCostInputs` (ADR-0025). This is the modelling-layer half of the split: it derives the existing system, property size band, design heat loss (floor-area proxy), radiator count, and whether a wet system can be reused — the catalogue math (Products) stays EPC-free. """ import copy from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.modelling.generators.heating_recommendation import ashp_cost_inputs from domain.modelling.products import AshpCostInputs, AshpExistingSystem from tests.domain.modelling._elmhurst_recommendation import ( parse_recommendation_summary, ) def test_mains_gas_dwelling_maps_to_a_reusable_wet_gas_system() -> None: # Arrange — a mains-gas regular boiler with radiators (90 m2, 7 habitable # rooms): an existing wet system the ASHP can reuse. epc = parse_recommendation_summary( "ashp_from_system_boiler_with_cylinder_001431_before.pdf" ) # Act inputs: AshpCostInputs = ashp_cost_inputs(epc) # Assert — gas, large (90 > 75 m2), 4.5 kW (90 x 0.05), 10 radiators # (7 habitable + 3), reusable wet system. assert inputs.existing_system is AshpExistingSystem.GAS assert inputs.is_small_property is False assert abs(inputs.design_heat_loss_kw - 4.5) <= 1e-9 assert inputs.radiator_count == 10 assert inputs.has_reusable_wet_system is True def test_electric_dwelling_has_no_reusable_wet_system() -> None: # Arrange — an electric storage-heater dwelling (no wet system). epc = parse_recommendation_summary( "hhr_storage_from_electric_storage_001431_before.pdf" ) # Act inputs: AshpCostInputs = ashp_cost_inputs(epc) # Assert — electric, so a full new wet distribution is needed. assert inputs.existing_system is AshpExistingSystem.ELECTRIC_STORAGE assert inputs.has_reusable_wet_system is False def test_classification_keys_on_fuel_not_the_mains_gas_flag() -> None: # Arrange — the 001431 electric fixtures all lodge mains_gas=True (gas is # available at the property) while heating electrically (fuel 30). The # classifier must key on the fuel, not the flag, or it would misread these # as gas and wrongly reuse a non-existent wet system. epc = parse_recommendation_summary( "hhr_storage_from_electric_storage_001431_before.pdf" ) # Act / Assert assert epc.sap_energy_source.mains_gas is True inputs: AshpCostInputs = ashp_cost_inputs(epc) assert inputs.existing_system is AshpExistingSystem.ELECTRIC_STORAGE assert inputs.has_reusable_wet_system is False def test_oil_and_lpg_dwellings_are_reusable_wet_systems() -> None: # Arrange — no oil/LPG fixture exists, so mutate an off-gas dwelling's main # fuel to oil (28) and LPG (27); both are wet boiler systems. base: EpcPropertyData = parse_recommendation_summary( "hhr_storage_from_electric_storage_001431_before.pdf" ) def _with_fuel(code: int) -> EpcPropertyData: clone: EpcPropertyData = copy.deepcopy(base) clone.sap_energy_source.mains_gas = False clone.sap_heating.main_heating_details[0].main_fuel_type = code clone.sap_heating.main_heating_details[0].sap_main_heating_code = 199 return clone # Act / Assert assert ashp_cost_inputs(_with_fuel(28)).existing_system is AshpExistingSystem.OIL assert ashp_cost_inputs(_with_fuel(27)).existing_system is AshpExistingSystem.LPG assert ashp_cost_inputs(_with_fuel(28)).has_reusable_wet_system is True