diff --git a/datatypes/epc/domain/mapper.py b/datatypes/epc/domain/mapper.py index 1afade5c..554bdda6 100644 --- a/datatypes/epc/domain/mapper.py +++ b/datatypes/epc/domain/mapper.py @@ -51,6 +51,11 @@ from datatypes.epc.schema.rdsap_schema_21_0_1 import ( RdSapSchema21_0_1, EnergyElement as EnergyElement_21_0_1, ) +from datatypes.epc.surveys.elmhurst_site_notes import ( + ElmhurstSiteNotes, + VentilationAndCooling as ElmhurstVentilation, + Window as ElmhurstWindow, +) from datatypes.epc.surveys.pashub_rdsap_site_notes import ( BuildingConstruction, BuildingMeasurements, @@ -200,6 +205,67 @@ class EpcPropertyDataMapper: sap_ventilation=_map_sap_ventilation(ventilation), ) + @staticmethod + def from_elmhurst_site_notes(survey: ElmhurstSiteNotes) -> EpcPropertyData: + pd = survey.property_details + built_form = _strip_code(survey.attachment) + property_type = _strip_code(survey.property_type) + + prefix = pd.house_number or pd.house_name or "" + address_line_1 = f"{prefix}, {pd.street}" if prefix else pd.street + + return EpcPropertyData( + dwelling_type=f"{built_form} {property_type.lower()}", + inspection_date=pd.inspection_date, + tenure=pd.tenure, + transaction_type=pd.transaction_type, + address_line_1=address_line_1, + post_town=pd.town, + postcode=pd.postcode, + report_reference=pd.reference_number, + roofs=[], + walls=[], + floors=[], + main_heating=[], + door_count=survey.door_count, + sap_heating=_map_elmhurst_sap_heating(survey), + sap_windows=[_map_elmhurst_window(w) for w in survey.windows], + sap_energy_source=SapEnergySource( + mains_gas=survey.meters.main_gas, + meter_type=survey.meters.electricity_meter_type, + pv_battery_count=0, + wind_turbines_count=1 if survey.renewables.wind_turbine_present else 0, + gas_smart_meter_present=survey.meters.gas_smart_meter, + is_dwelling_export_capable=survey.renewables.export_capable_meter, + wind_turbines_terrain_type=survey.renewables.wind_turbines_terrain_type, + electricity_smart_meter_present=survey.meters.electricity_smart_meter, + ), + sap_building_parts=[_map_elmhurst_building_part(survey)], + solar_water_heating=survey.renewables.solar_water_heating, + has_hot_water_cylinder=survey.water_heating.hot_water_cylinder_present, + has_fixed_air_conditioning=survey.ventilation.fixed_space_cooling, + wet_rooms_count=0, + extensions_count=0, + heated_rooms_count=survey.heated_habitable_rooms, + open_chimneys_count=survey.ventilation.open_chimneys_count, + habitable_rooms_count=survey.habitable_rooms, + insulated_door_count=survey.insulated_door_count, + cfl_fixed_lighting_bulbs_count=survey.lighting.cfl_count, + led_fixed_lighting_bulbs_count=survey.lighting.led_count, + incandescent_fixed_lighting_bulbs_count=survey.lighting.incandescent_count, + total_floor_area_m2=round( + sum(f.area_m2 for f in survey.dimensions.floors), 2 + ), + built_form=built_form, + property_type=property_type, + has_conservatory=survey.has_conservatory, + blocked_chimneys_count=survey.ventilation.blocked_chimneys_count, + number_of_storeys=survey.number_of_storeys, + hydro=survey.renewables.hydro_electricity_generated_kwh > 0, + photovoltaic_array=survey.renewables.photovoltaic_panel != "None", + sap_ventilation=_map_elmhurst_ventilation(survey.ventilation), + ) + @staticmethod def from_rdsap_schema_17_0(schema: RdSapSchema17_0) -> EpcPropertyData: es = schema.sap_energy_source @@ -1453,6 +1519,12 @@ class EpcPropertyDataMapper: # --------------------------------------------------------------------------- +def _strip_code(value: str) -> str: + """Strip leading uppercase code from Elmhurst coded strings, e.g. 'CA Cavity' → 'Cavity'.""" + parts = value.split(" ", 1) + return parts[1] if len(parts) > 1 else value + + def _extract_age_band(age_range: str) -> str: """Return the letter code from a site-notes age range, e.g. 'I: 1996 - 2002' → 'I'.""" return age_range.split(":")[0].strip() @@ -1617,3 +1689,101 @@ def _map_sap_ventilation(ventilation: Ventilation) -> SapVentilation: flueless_gas_fires_count=ventilation.number_of_flueless_gas_fires, ventilation_in_pcdf_database=ventilation.ventilation_in_pcdf_database, ) + + +def _map_elmhurst_building_part(survey: ElmhurstSiteNotes) -> SapBuildingPart: + dims = survey.dimensions + floor_dims = [ + SapFloorDimension( + room_height_m=f.room_height_m, + total_floor_area_m2=f.area_m2, + party_wall_length_m=f.party_wall_length_m, + heat_loss_perimeter_m=f.heat_loss_perimeter_m, + floor=i, + ) + for i, f in enumerate(dims.floors) + ] + return SapBuildingPart( + identifier="main", + construction_age_band=_strip_code(survey.construction_age_band), + wall_construction=_strip_code(survey.walls.wall_type), + wall_insulation_type=_strip_code(survey.walls.insulation), + wall_thickness_measured=not survey.walls.thickness_unknown, + party_wall_construction=_strip_code(survey.walls.party_wall_type), + sap_floor_dimensions=floor_dims, + wall_thickness_mm=survey.walls.thickness_mm, + roof_insulation_location=_strip_code(survey.roof.insulation), + roof_insulation_thickness=survey.roof.insulation_thickness_mm, + floor_type=_strip_code(survey.floor.location), + floor_construction_type=_strip_code(survey.floor.floor_type), + floor_insulation_type_str=_strip_code(survey.floor.insulation), + floor_u_value_known=survey.floor.u_value_known, + ) + + +def _map_elmhurst_window(w: ElmhurstWindow) -> SapWindow: + return SapWindow( + pvc_frame=w.frame_type or "", + glazing_gap=w.glazing_gap or "", + orientation=w.orientation, + window_type="Window", + glazing_type=w.glazing_type, + window_width=w.width_m, + window_height=w.height_m, + draught_proofed=w.draught_proofed, + window_location=w.building_part, + window_wall_type=w.location, + permanent_shutters_present=w.permanent_shutters, + ) + + +def _map_elmhurst_sap_heating(survey: ElmhurstSiteNotes) -> SapHeating: + mh = survey.main_heating + sap_control = mh.heating_controls_sap + control = ( + sap_control.split(", ", 1)[1] + if sap_control.startswith("SAP code") and ", " in sap_control + else sap_control + ) + shower_outlets = ( + ShowerOutlets( + shower_outlet=ShowerOutlet( + shower_outlet_type=survey.baths_and_showers.showers[0].outlet_type + ) + ) + if survey.baths_and_showers.showers + else None + ) + return SapHeating( + instantaneous_wwhrs=InstantaneousWwhrs(), + main_heating_details=[ + MainHeatingDetail( + has_fghrs=survey.renewables.flue_gas_heat_recovery_present, + main_fuel_type=mh.fuel_type, + heat_emitter_type=mh.heat_emitter, + emitter_temperature=mh.design_flow_temperature, + fan_flue_present=mh.fan_assisted_flue, + main_heating_control=control, + central_heating_pump_age_str=mh.heat_pump_age, + ) + ], + has_fixed_air_conditioning=survey.ventilation.fixed_space_cooling, + shower_outlets=shower_outlets, + cylinder_size=None if not survey.water_heating.hot_water_cylinder_present else survey.water_heating.water_heating_code, + ) + + +def _map_elmhurst_ventilation(v: ElmhurstVentilation) -> SapVentilation: + return SapVentilation( + ventilation_type=None, + draught_lobby=v.draught_lobby != "Not present", + pressure_test=v.pressure_test_method, + open_flues_count=v.open_flues_count, + closed_flues_count=v.open_chimneys_closed_fire_count, + boiler_flues_count=v.solid_fuel_boiler_flues_count, + other_flues_count=v.other_heater_flues_count, + extract_fans_count=v.extract_fans_count, + passive_vents_count=v.passive_vents_count, + flueless_gas_fires_count=v.flueless_gas_fires_count, + ventilation_in_pcdf_database=None, + )