"""Test fixtures for EpcMlTransform tests. `make_minimal_sap10_epc()` constructs a valid EpcPropertyData with the smallest sensible defaults for required fields; target values are passed by kwarg so each test parametrises only the fields it cares about. `make_window()` builds a SapWindow with sensible SAP10 defaults; pass the fields relevant to the test (orientation / dimensions / glazing / draught proofing). """ from datetime import date from typing import Optional, Union from datatypes.epc.domain.epc_property_data import ( BuildingPartIdentifier, EpcPropertyData, InstantaneousWwhrs, MainHeatingDetail, PhotovoltaicArray, PhotovoltaicSupply, PhotovoltaicSupplyNoneOrNoDetails, PvBatteries, PvBattery, RenewableHeatIncentive, SapBuildingPart, SapEnergySource, SapFloorDimension, SapHeating, SapRoofWindow, SapRoomInRoof, SapVentilation, SapWindow, WindTurbineDetails, WindowTransmissionDetails, ) def make_pv_array( *, peak_power: float = 2.0, pitch: int = 2, orientation: int = 5, overshading: int = 1, ) -> PhotovoltaicArray: """Build a PhotovoltaicArray with SAP10 defaults (2 kW, S-facing).""" return PhotovoltaicArray( peak_power=peak_power, pitch=pitch, orientation=orientation, overshading=overshading, ) def make_main_heating_detail( *, main_fuel_type: Union[int, str] = 26, # mains gas (not community) heat_emitter_type: Union[int, str] = 1, main_heating_control: Union[int, str] = 2106, emitter_temperature: Union[int, str] = 1, main_heating_category: Optional[int] = 2, has_fghrs: bool = False, fan_flue_present: Optional[bool] = True, boiler_flue_type: Optional[int] = 2, central_heating_pump_age: Optional[int] = 0, main_heating_number: Optional[int] = 1, main_heating_index_number: Optional[int] = None, main_heating_data_source: Optional[int] = None, sap_main_heating_code: Optional[int] = None, ) -> MainHeatingDetail: """Build a MainHeatingDetail with SAP10 API defaults (mains gas boiler). Pass `main_heating_index_number` to point at a PCDB record (typical cert convention is `main_heating_data_source=1` + a PCDB pointer for PCDB-listed systems; `main_heating_data_source=2` + `sap_main_heating_ code` for Table 4b-lodged systems).""" return MainHeatingDetail( has_fghrs=has_fghrs, main_fuel_type=main_fuel_type, heat_emitter_type=heat_emitter_type, emitter_temperature=emitter_temperature, main_heating_control=main_heating_control, fan_flue_present=fan_flue_present, boiler_flue_type=boiler_flue_type, central_heating_pump_age=central_heating_pump_age, main_heating_number=main_heating_number, main_heating_category=main_heating_category, main_heating_index_number=main_heating_index_number, main_heating_data_source=main_heating_data_source, sap_main_heating_code=sap_main_heating_code, ) def make_sap_heating( *, main_heating_details: Optional[list[MainHeatingDetail]] = None, has_fixed_air_conditioning: bool = False, water_heating_code: Optional[int] = 901, water_heating_fuel: Optional[int] = 26, cylinder_size: Optional[Union[int, str]] = None, cylinder_insulation_type: Optional[int] = None, cylinder_insulation_thickness_mm: Optional[int] = None, cylinder_thermostat: Optional[str] = None, secondary_fuel_type: Optional[int] = None, secondary_heating_type: Optional[int] = None, number_baths: Optional[int] = None, electric_shower_count: Optional[int] = None, mixer_shower_count: Optional[int] = None, ) -> SapHeating: """Build a SapHeating with SAP10 API defaults.""" return SapHeating( instantaneous_wwhrs=InstantaneousWwhrs(), main_heating_details=main_heating_details if main_heating_details is not None else [make_main_heating_detail()], has_fixed_air_conditioning=has_fixed_air_conditioning, water_heating_code=water_heating_code, water_heating_fuel=water_heating_fuel, cylinder_size=cylinder_size, cylinder_insulation_type=cylinder_insulation_type, cylinder_insulation_thickness_mm=cylinder_insulation_thickness_mm, cylinder_thermostat=cylinder_thermostat, secondary_fuel_type=secondary_fuel_type, secondary_heating_type=secondary_heating_type, number_baths=number_baths, electric_shower_count=electric_shower_count, mixer_shower_count=mixer_shower_count, ) def make_floor_dimension( *, total_floor_area_m2: float = 50.0, room_height_m: float = 2.5, party_wall_length_m: float = 5.0, heat_loss_perimeter_m: float = 20.0, floor: Optional[int] = 0, ) -> SapFloorDimension: """Build a SapFloorDimension with sensible defaults.""" return SapFloorDimension( room_height_m=room_height_m, total_floor_area_m2=total_floor_area_m2, party_wall_length_m=party_wall_length_m, heat_loss_perimeter_m=heat_loss_perimeter_m, floor=floor, ) def make_building_part( *, identifier: BuildingPartIdentifier = BuildingPartIdentifier.MAIN, construction_age_band: str = "B", wall_construction: Union[int, str] = 3, wall_insulation_type: Union[int, str] = 2, wall_thickness_measured: bool = True, party_wall_construction: Union[int, str] = 1, roof_construction: Optional[int] = 4, floor_dimensions: Optional[list[SapFloorDimension]] = None, sap_room_in_roof: Optional[SapRoomInRoof] = None, ) -> SapBuildingPart: """Build a SapBuildingPart with sensible SAP10 defaults.""" return SapBuildingPart( identifier=identifier, construction_age_band=construction_age_band, wall_construction=wall_construction, wall_insulation_type=wall_insulation_type, wall_thickness_measured=wall_thickness_measured, party_wall_construction=party_wall_construction, roof_construction=roof_construction, sap_floor_dimensions=floor_dimensions if floor_dimensions is not None else [make_floor_dimension()], sap_room_in_roof=sap_room_in_roof, ) def make_window( *, orientation: Union[int, str] = 5, # SAP10: 1=N, 2=NE, 3=E, 4=SE, 5=S, 6=SW, 7=W, 8=NW width: float = 1.0, height: float = 1.0, draught_proofed: bool = True, glazing_type: Union[int, str] = 2, # "double glazing 2002-2022" glazing_gap: Union[int, str] = "16+", window_type: Union[int, str] = 1, window_location: Union[int, str] = 0, window_wall_type: Union[int, str] = 1, permanent_shutters_present: Union[bool, str] = False, frame_material: Optional[str] = "PVC", frame_factor: Optional[float] = 0.7, # SAP10.2 Table 6c PVC default; # mirrors the Elmhurst mapper's # surfaced value from Summary §11. window_transmission_details: Optional[WindowTransmissionDetails] = None, solar_transmittance: Optional[float] = None, u_value: float = 2.8, ) -> SapWindow: """Build a SapWindow with SAP10 defaults; override the fields the test cares about. `solar_transmittance` is a shortcut that builds a `WindowTransmissionDetails(u_value, data_source=1, solar_transmittance)` when `window_transmission_details` isn't supplied directly — keeps Elmhurst §6 fixture literals tight. """ if window_transmission_details is None and solar_transmittance is not None: window_transmission_details = WindowTransmissionDetails( u_value=u_value, data_source=1, solar_transmittance=solar_transmittance, ) return SapWindow( frame_material=frame_material, glazing_gap=glazing_gap, orientation=orientation, window_type=window_type, glazing_type=glazing_type, window_width=width, window_height=height, draught_proofed=draught_proofed, window_location=window_location, window_wall_type=window_wall_type, permanent_shutters_present=permanent_shutters_present, frame_factor=frame_factor, window_transmission_details=window_transmission_details, ) def make_minimal_sap10_epc( *, energy_rating_current: Optional[int] = None, co2_emissions_current: Optional[float] = None, energy_consumption_current: Optional[int] = None, space_heating_kwh: float = 0.0, water_heating_kwh: float = 0.0, total_floor_area_m2: float = 70.0, door_count: int = 0, habitable_rooms_count: int = 0, heated_rooms_count: int = 0, wet_rooms_count: int = 0, extensions_count: int = 0, open_chimneys_count: int = 0, insulated_door_count: int = 0, cfl_fixed_lighting_bulbs_count: int = 0, led_fixed_lighting_bulbs_count: int = 0, incandescent_fixed_lighting_bulbs_count: int = 0, low_energy_fixed_lighting_bulbs_count: Optional[int] = None, solar_water_heating: bool = False, has_hot_water_cylinder: bool = False, has_fixed_air_conditioning: bool = False, percent_draughtproofed: Optional[int] = None, energy_rating_average: Optional[int] = None, environmental_impact_current: Optional[int] = None, dwelling_type: str = "Mid-terrace house", tenure: str = "1", transaction_type: str = "1", property_type: Optional[str] = None, built_form: Optional[str] = None, region_code: Optional[str] = None, country_code: Optional[str] = None, sap_windows: Optional[list[SapWindow]] = None, sap_roof_windows: Optional[list[SapRoofWindow]] = None, sap_building_parts: Optional[list[SapBuildingPart]] = None, sap_heating: Optional[SapHeating] = None, photovoltaic_arrays: Optional[list[PhotovoltaicArray]] = None, photovoltaic_supply_percent_roof_area: Optional[int] = None, mains_gas: bool = True, electricity_smart_meter_present: bool = False, gas_smart_meter_present: bool = False, is_dwelling_export_capable: bool = False, pv_battery_count: int = 0, pv_battery_capacity_per_unit_kwh: Optional[float] = None, wind_turbines_count: int = 0, mechanical_ventilation: Optional[int] = None, mechanical_vent_duct_type: Optional[int] = None, blocked_chimneys_count: Optional[int] = None, pressure_test: Optional[int] = None, sap_ventilation: Optional[SapVentilation] = None, postcode: str = "A1 1AA", ) -> EpcPropertyData: """Construct a minimal valid SAP10 EpcPropertyData with parametrisable targets.""" return EpcPropertyData( dwelling_type=dwelling_type, inspection_date=date(2025, 6, 1), tenure=tenure, transaction_type=transaction_type, address_line_1="1 Test Street", postcode=postcode, post_town="Testtown", roofs=[], walls=[], floors=[], main_heating=[], door_count=door_count, sap_heating=sap_heating if sap_heating is not None else SapHeating( instantaneous_wwhrs=InstantaneousWwhrs(), main_heating_details=[], has_fixed_air_conditioning=False, ), sap_windows=list(sap_windows) if sap_windows is not None else [], sap_roof_windows=( list(sap_roof_windows) if sap_roof_windows is not None else None ), sap_energy_source=SapEnergySource( mains_gas=mains_gas, meter_type="Single", pv_battery_count=pv_battery_count, wind_turbines_count=wind_turbines_count, gas_smart_meter_present=gas_smart_meter_present, is_dwelling_export_capable=is_dwelling_export_capable, wind_turbines_terrain_type="Suburban", electricity_smart_meter_present=electricity_smart_meter_present, photovoltaic_arrays=list(photovoltaic_arrays) if photovoltaic_arrays is not None else None, photovoltaic_supply=( PhotovoltaicSupply( none_or_no_details=PhotovoltaicSupplyNoneOrNoDetails( percent_roof_area=photovoltaic_supply_percent_roof_area ) ) if photovoltaic_supply_percent_roof_area is not None else None ), pv_batteries=( PvBatteries( pv_battery=PvBattery( battery_capacity=pv_battery_capacity_per_unit_kwh ) ) if pv_battery_capacity_per_unit_kwh is not None else None ), wind_turbine_details=( WindTurbineDetails(hub_height=5.0, rotor_diameter=2.0) if wind_turbines_count > 0 else None ), ), sap_building_parts=list(sap_building_parts) if sap_building_parts is not None else [], solar_water_heating=solar_water_heating, has_hot_water_cylinder=has_hot_water_cylinder, has_fixed_air_conditioning=has_fixed_air_conditioning, wet_rooms_count=wet_rooms_count, extensions_count=extensions_count, heated_rooms_count=heated_rooms_count, open_chimneys_count=open_chimneys_count, habitable_rooms_count=habitable_rooms_count, insulated_door_count=insulated_door_count, cfl_fixed_lighting_bulbs_count=cfl_fixed_lighting_bulbs_count, led_fixed_lighting_bulbs_count=led_fixed_lighting_bulbs_count, incandescent_fixed_lighting_bulbs_count=incandescent_fixed_lighting_bulbs_count, low_energy_fixed_lighting_bulbs_count=low_energy_fixed_lighting_bulbs_count, total_floor_area_m2=total_floor_area_m2, sap_version=10.2, energy_rating_current=energy_rating_current, co2_emissions_current=co2_emissions_current, energy_consumption_current=energy_consumption_current, percent_draughtproofed=percent_draughtproofed, energy_rating_average=energy_rating_average, environmental_impact_current=environmental_impact_current, property_type=property_type, built_form=built_form, region_code=region_code, country_code=country_code, mechanical_ventilation=mechanical_ventilation, mechanical_vent_duct_type=mechanical_vent_duct_type, blocked_chimneys_count=blocked_chimneys_count, pressure_test=pressure_test, sap_ventilation=sap_ventilation, renewable_heat_incentive=RenewableHeatIncentive( space_heating_kwh=space_heating_kwh, water_heating_kwh=water_heating_kwh, ), )