mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
fix(debug-tool): read the domain field names, not the schema ones
The first cut of elmhurst_input_sheet.py introspected the `schema` dataclasses (rdsap_schema_*.py) but the mapper emits the `epc_property_data` domain types, whose fields differ (wall_thickness_mm not wall_thickness; total_floor_area_m2 not total_floor_area; frame_material not pvc_frame; cylinder_insulation_thickness_mm; SapRoomInRoof has gable_*_length_m not insulation/roof_room_connected). Worse, the getattr-with-None-default helper printed None over real data, nearly sending a debug session chasing a non-existent "dimensions dropped" mapper bug on cert 2100 (the dims map fine; that cert's error is elsewhere). Switched to direct attribute access so a future rename fails loudly, fixed every field name against the live domain objects, and added roof_construction_type / floor_type for context. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2c126b2a62
commit
7e9231b36b
1 changed files with 64 additions and 71 deletions
|
|
@ -43,14 +43,6 @@ def _num(v: Any) -> Any:
|
|||
return getattr(v, "value", v)
|
||||
|
||||
|
||||
def _g(obj: Any, *names: str, default: Any = None) -> Any:
|
||||
"""First present attribute from `names`, else `default`."""
|
||||
for n in names:
|
||||
if hasattr(obj, n):
|
||||
return getattr(obj, n)
|
||||
return default
|
||||
|
||||
|
||||
def _resolve(cert_arg: str) -> Path:
|
||||
p = Path(cert_arg)
|
||||
if p.suffix == ".json" and p.exists():
|
||||
|
|
@ -137,46 +129,48 @@ def render(cert: str, doc: dict[str, Any]) -> str:
|
|||
w(f" SECONDARY: {epc.secondary_heating.description}")
|
||||
|
||||
# --- building parts / dimensions -----------------------------------
|
||||
# NB direct attribute access (not getattr-with-default) so a future
|
||||
# domain rename fails loudly here rather than silently printing None
|
||||
# over real data. Field names are the `epc_property_data` domain
|
||||
# types the mapper emits (NOT the `schema` dataclasses).
|
||||
w("\n## Building parts / dimensions")
|
||||
for bp in epc.sap_building_parts or []:
|
||||
name = _g(bp, "identifier", default=f"part {_g(bp, 'building_part_number')}")
|
||||
age = _g(bp, "construction_age_band")
|
||||
w(f"### {name} (part {_g(bp, 'building_part_number')}, age {age})")
|
||||
w(f"### {bp.identifier} (part {bp.building_part_number}, age {bp.construction_age_band})")
|
||||
w(
|
||||
f" wall_construction={_g(bp, 'wall_construction')} "
|
||||
f"insulation_type={_g(bp, 'wall_insulation_type')} "
|
||||
f"ins_thick={_g(bp, 'wall_insulation_thickness')} "
|
||||
f"wall_thickness={_g(bp, 'wall_thickness')}mm "
|
||||
f"measured={_g(bp, 'wall_thickness_measured')} "
|
||||
f"dry_lined={_g(bp, 'wall_dry_lined')}"
|
||||
f" wall_construction={bp.wall_construction} "
|
||||
f"insulation_type={bp.wall_insulation_type} "
|
||||
f"ins_thick={bp.wall_insulation_thickness} "
|
||||
f"wall_thickness={bp.wall_thickness_mm}mm "
|
||||
f"measured={bp.wall_thickness_measured} "
|
||||
f"dry_lined={bp.wall_dry_lined}"
|
||||
)
|
||||
w(f" party_wall_construction={_g(bp, 'party_wall_construction')}")
|
||||
w(f" party_wall_construction={bp.party_wall_construction}")
|
||||
w(
|
||||
f" roof_construction={_g(bp, 'roof_construction')} "
|
||||
f"ins_location={_g(bp, 'roof_insulation_location')} "
|
||||
f"ins_thick={_g(bp, 'roof_insulation_thickness')}"
|
||||
f" roof_construction={bp.roof_construction} ({bp.roof_construction_type}) "
|
||||
f"ins_location={bp.roof_insulation_location} "
|
||||
f"ins_thick={bp.roof_insulation_thickness}"
|
||||
)
|
||||
w(
|
||||
f" floor_heat_loss={_g(bp, 'floor_heat_loss')} "
|
||||
f"floor_ins_thick={_g(bp, 'floor_insulation_thickness')}"
|
||||
f" floor_heat_loss={bp.floor_heat_loss} ({bp.floor_type}) "
|
||||
f"floor_ins_thick={bp.floor_insulation_thickness}"
|
||||
)
|
||||
rir = _g(bp, "sap_room_in_roof")
|
||||
rir = bp.sap_room_in_roof
|
||||
if rir is not None:
|
||||
w(
|
||||
f" ROOM-IN-ROOF: floor_area={_num(_g(rir, 'floor_area'))} "
|
||||
f"age={_g(rir, 'construction_age_band')} "
|
||||
f"insulation={_g(rir, 'insulation')} "
|
||||
f"connected={_g(rir, 'roof_room_connected')}"
|
||||
f" ROOM-IN-ROOF: floor_area={_num(rir.floor_area)} "
|
||||
f"age={rir.construction_age_band} "
|
||||
f"gable1={rir.gable_1_length_m}x{rir.gable_1_height_m}m "
|
||||
f"gable2={rir.gable_2_length_m}x{rir.gable_2_height_m}m "
|
||||
f"common_wall={rir.common_wall_length_m}m"
|
||||
)
|
||||
floor_dims: list[Any] = _g(bp, "sap_floor_dimensions", default=[]) or []
|
||||
for fd in floor_dims:
|
||||
for fd in bp.sap_floor_dimensions or []:
|
||||
w(
|
||||
f" floor {_g(fd, 'floor')}: area={_num(_g(fd, 'total_floor_area'))} "
|
||||
f"height={_num(_g(fd, 'room_height'))} "
|
||||
f"HLP={_num(_g(fd, 'heat_loss_perimeter'))} "
|
||||
f"party_wall_len={_num(_g(fd, 'party_wall_length'))} "
|
||||
f"floor_constr={_g(fd, 'floor_construction')} "
|
||||
f"floor_ins={_g(fd, 'floor_insulation')}"
|
||||
f" floor {fd.floor}: area={fd.total_floor_area_m2} "
|
||||
f"height={fd.room_height_m} "
|
||||
f"HLP={fd.heat_loss_perimeter_m} "
|
||||
f"party_wall_len={fd.party_wall_length_m} "
|
||||
f"floor_constr={fd.floor_construction} floor_ins={fd.floor_insulation} "
|
||||
f"exposed={fd.is_exposed_floor}"
|
||||
)
|
||||
|
||||
# --- windows -------------------------------------------------------
|
||||
|
|
@ -184,15 +178,15 @@ def render(cert: str, doc: dict[str, Any]) -> str:
|
|||
w(f"\n## Windows ({len(windows)})")
|
||||
for i, win in enumerate(windows):
|
||||
w(
|
||||
f" W{i}: {_g(win, 'window_width')}x{_g(win, 'window_height')}m "
|
||||
f"orient={_g(win, 'orientation')} "
|
||||
f"glazing_type={_g(win, 'glazing_type')} "
|
||||
f"gap={_g(win, 'glazing_gap')} "
|
||||
f"pvc={_g(win, 'pvc_frame')} "
|
||||
f"draught={_g(win, 'draught_proofed')} "
|
||||
f"loc(bp)={_g(win, 'window_location')} "
|
||||
f"wall_type={_g(win, 'window_wall_type')} "
|
||||
f"frame_factor={_g(win, 'frame_factor')}"
|
||||
f" W{i}: {win.window_width}x{win.window_height}m "
|
||||
f"orient={win.orientation} "
|
||||
f"glazing_type={win.glazing_type} "
|
||||
f"gap={win.glazing_gap} "
|
||||
f"frame={win.frame_material} "
|
||||
f"draught={win.draught_proofed} "
|
||||
f"loc(bp)={win.window_location} "
|
||||
f"wall_type={win.window_wall_type} "
|
||||
f"frame_factor={win.frame_factor}"
|
||||
)
|
||||
|
||||
# --- doors / heating / water / vent --------------------------------
|
||||
|
|
@ -202,40 +196,39 @@ def render(cert: str, doc: dict[str, Any]) -> str:
|
|||
f"insulated_door_count={epc.insulated_door_count}"
|
||||
)
|
||||
sh = epc.sap_heating
|
||||
mh_details: list[Any] = _g(sh, "main_heating_details", default=[]) or []
|
||||
for mh in mh_details:
|
||||
for mh in sh.main_heating_details or []:
|
||||
w(
|
||||
f" MAIN: sap_code={_g(mh, 'sap_main_heating_code')} "
|
||||
f"fuel={_g(mh, 'main_fuel_type')} "
|
||||
f"category={_g(mh, 'main_heating_category')} "
|
||||
f"emitter={_g(mh, 'heat_emitter_type')} "
|
||||
f"emit_temp={_g(mh, 'emitter_temperature')} "
|
||||
f"control={_g(mh, 'main_heating_control')} "
|
||||
f"fghrs={_g(mh, 'has_fghrs')} "
|
||||
f"fan_flue={_g(mh, 'fan_flue_present')} "
|
||||
f"flue_type={_g(mh, 'boiler_flue_type')} "
|
||||
f"pump_age={_g(mh, 'central_heating_pump_age')} "
|
||||
f"data_source={_g(mh, 'main_heating_data_source')} "
|
||||
f"idx={_g(mh, 'main_heating_index_number')} "
|
||||
f"fraction={_g(mh, 'main_heating_fraction')}"
|
||||
f" MAIN: sap_code={mh.sap_main_heating_code} "
|
||||
f"fuel={mh.main_fuel_type} "
|
||||
f"category={mh.main_heating_category} "
|
||||
f"emitter={mh.heat_emitter_type} "
|
||||
f"emit_temp={mh.emitter_temperature} "
|
||||
f"control={mh.main_heating_control} "
|
||||
f"fghrs={mh.has_fghrs} "
|
||||
f"fan_flue={mh.fan_flue_present} "
|
||||
f"flue_type={mh.boiler_flue_type} "
|
||||
f"pump_age={mh.central_heating_pump_age} "
|
||||
f"data_source={mh.main_heating_data_source} "
|
||||
f"idx={mh.main_heating_index_number} "
|
||||
f"fraction={mh.main_heating_fraction}"
|
||||
)
|
||||
w(
|
||||
f" WATER: code={_g(sh, 'water_heating_code')} "
|
||||
f"fuel={_g(sh, 'water_heating_fuel')} "
|
||||
f"cylinder_size={_g(sh, 'cylinder_size')} "
|
||||
f" WATER: code={sh.water_heating_code} "
|
||||
f"fuel={sh.water_heating_fuel} "
|
||||
f"cylinder_size={sh.cylinder_size} "
|
||||
f"has_cyl={str(epc.has_hot_water_cylinder).lower()} "
|
||||
f"cyl_ins_type={_g(sh, 'cylinder_insulation_type')} "
|
||||
f"cyl_ins_thick={_g(sh, 'cylinder_insulation_thickness')} "
|
||||
f"immersion={_g(sh, 'immersion_heating_type')} "
|
||||
f"cyl_ins_type={sh.cylinder_insulation_type} "
|
||||
f"cyl_ins_thick={sh.cylinder_insulation_thickness_mm} "
|
||||
f"immersion={sh.immersion_heating_type} "
|
||||
f"solar_wh={str(epc.solar_water_heating).lower()} "
|
||||
f"secondary_fuel={_g(sh, 'secondary_fuel_type')} "
|
||||
f"secondary_type={_g(sh, 'secondary_heating_type')}"
|
||||
f"secondary_fuel={sh.secondary_fuel_type} "
|
||||
f"secondary_type={sh.secondary_heating_type}"
|
||||
)
|
||||
es = epc.sap_energy_source
|
||||
w(
|
||||
f" ENERGY SOURCE: mains_gas={_g(es, 'mains_gas')} "
|
||||
f"meter_type={_g(es, 'meter_type')} "
|
||||
f"wind_turbines={_g(es, 'wind_turbines_count')} "
|
||||
f" ENERGY SOURCE: mains_gas={es.mains_gas} "
|
||||
f"meter_type={es.meter_type} "
|
||||
f"wind_turbines={es.wind_turbines_count} "
|
||||
f"pv_raw={json.dumps(doc.get('sap_energy_source', {}).get('photovoltaic_supply'))}"
|
||||
)
|
||||
w(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue