"""The dwelling interpretation that feeds `Products.boiler_bundle_cost` / `tune_up_cost` — reading an `EpcPropertyData` into typed cost inputs (ADR-0027). The modelling-layer half of the split: it derives the radiator count, which standard-control parts are already fitted (from the SAP Table 4e control code), whether the boiler upgrade fires a controls change, and which cylinder fixes apply — the catalogue math (Products) stays EPC-free. """ from datatypes.epc.domain.epc_property_data import EpcPropertyData from domain.modelling.generators.heating_recommendation import ( boiler_cost_inputs, tune_up_cost_inputs, ) from domain.modelling.products import BoilerCostInputs, TuneUpCostInputs from tests.domain.modelling._elmhurst_recommendation import ( parse_recommendation_summary, ) def test_tune_up_inputs_from_no_controls_charge_every_part() -> None: # Arrange — control 2101 ("no control"): no programmer, room thermostat or # TRVs fitted; an uninsulated, un-thermostatted cylinder; 10 radiators. epc: EpcPropertyData = parse_recommendation_summary( "tune_up_from_2101_001431_before.pdf" ) # Act inputs: TuneUpCostInputs = tune_up_cost_inputs(epc, is_zoned=False) # Assert assert inputs == TuneUpCostInputs( is_zoned=False, radiator_count=10, has_programmer=False, has_room_thermostat=False, has_trvs=False, needs_cylinder_jacket=True, needs_cylinder_thermostat=True, ) def test_tune_up_inputs_read_existing_control_parts() -> None: # Arrange — control 2113 ("room thermostat and TRVs"): already has a room # thermostat + TRVs, only the programmer is missing. The is_zoned flag is # passed through. epc: EpcPropertyData = parse_recommendation_summary( "tune_up_from_2113_001431_before.pdf" ) # Act inputs: TuneUpCostInputs = tune_up_cost_inputs(epc, is_zoned=True) # Assert — so the standard cost would charge only the programmer. assert inputs.is_zoned is True assert inputs.has_programmer is False assert inputs.has_room_thermostat is True assert inputs.has_trvs is True def test_boiler_inputs_flag_a_controls_upgrade_for_inadequate_controls() -> None: # Arrange — a combi (no cylinder) with inadequate controls (2111 "TRVs and # bypass", no room thermostat): the boiler upgrade also fires the standard # controls, which already has TRVs but no programmer/room thermostat. epc: EpcPropertyData = parse_recommendation_summary( "boiler_combi_gas_001431_before.pdf" ) # Act inputs: BoilerCostInputs = boiler_cost_inputs(epc) # Assert assert inputs == BoilerCostInputs( upgrades_controls=True, radiator_count=7, has_programmer=False, has_room_thermostat=False, has_trvs=True, needs_cylinder_jacket=False, needs_cylinder_thermostat=False, ) def test_boiler_inputs_no_controls_upgrade_when_already_adequate() -> None: # Arrange — a gas boiler with a cylinder and already-adequate controls # (2106): the boiler doesn't fire a controls change, but both cylinder fixes # apply (uninsulated, un-thermostatted). epc: EpcPropertyData = parse_recommendation_summary( "boiler_cyl_gas_001431_before.pdf" ) # Act inputs: BoilerCostInputs = boiler_cost_inputs(epc) # Assert assert inputs.upgrades_controls is False assert inputs.needs_cylinder_jacket is True assert inputs.needs_cylinder_thermostat is True