From 5c4a8d90948c8ba8fc2dbe16f6e3d489e0a2a882 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Fri, 26 Jun 2026 18:42:51 +0000 Subject: [PATCH] =?UTF-8?q?test:=207=20calculator-read=20EPC=20fields=20mu?= =?UTF-8?q?st=20round-trip=20=F0=9F=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit community heating fuel + CHP fraction, alt-wall is_sheltered, wall insulation thermal conductivity, pv_diverter_present, measured cylinder volume, AP50 air permeability — all calculator-read, all silently dropped on save. FE columns now live; assert deep-equal round-trip and drop their coverage-guard allow-list entries so the guard enforces reconstruction. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../test_epc_persistence_field_coverage.py | 11 ----- tests/repositories/epc/test_epc_round_trip.py | 48 +++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/tests/repositories/epc/test_epc_persistence_field_coverage.py b/tests/repositories/epc/test_epc_persistence_field_coverage.py index 56606d31..6f9481d8 100644 --- a/tests/repositories/epc/test_epc_persistence_field_coverage.py +++ b/tests/repositories/epc/test_epc_persistence_field_coverage.py @@ -62,17 +62,6 @@ _UNPERSISTED_ALLOWLIST: dict[str, str] = { "SapRoomInRoof.gable_2_height_m": "FE child table pending — room-in-roof detail", "SapRoomInRoof.gable_2_length_m": "FE child table pending — room-in-roof detail", "SapRoomInRoofSurface": "FE child table pending — room-in-roof surface detail", - # --- Nested gaps surfaced by the recursive guard (pre-existing; this guard - # only just became able to see them). The calculator READS these, so they are - # scoring-relevant silent-drop gaps awaiting FE columns — tracked follow-up, - # docs/migrations/epc-property-round-trip-fidelity.md §"recursive-guard gaps". - "MainHeatingDetail.community_heating_boiler_fuel_type": "calc-read; no FE column — tracked follow-up", - "MainHeatingDetail.community_heating_chp_fraction": "calc-read; no FE column — tracked follow-up", - "SapAlternativeWall.is_sheltered": "calc-read; no FE column — tracked follow-up", - "SapBuildingPart.wall_insulation_thermal_conductivity": "calc-read; no FE column — tracked follow-up", - "SapEnergySource.pv_diverter_present": "calc-read; no FE column — tracked follow-up", - "SapHeating.cylinder_volume_measured_l": "calc-read; no FE column — tracked follow-up", - "SapVentilation.air_permeability_ap50_m3_h_m2": "calc-read; no FE column — tracked follow-up", # --- Nested gaps the calculator does NOT read (dormant); no FE column. "SapAlternativeWall.is_basement": "dormant — not read by the calculator; no FE column", "SapBuildingPart.floor_u_value": "dormant — not read by the calculator (column exists, unread)", diff --git a/tests/repositories/epc/test_epc_round_trip.py b/tests/repositories/epc/test_epc_round_trip.py index ab6b7f65..5def5e7e 100644 --- a/tests/repositories/epc/test_epc_round_trip.py +++ b/tests/repositories/epc/test_epc_round_trip.py @@ -124,6 +124,54 @@ def test_photovoltaic_arrays_round_trip(db_engine: Engine) -> None: assert reloaded == original +def test_calculator_read_fields_round_trip(db_engine: Engine) -> None: + # Seven fields the SAP calculator reads (cert_to_inputs / heat_transmission) + # that had no DB column, so they were silently dropped on save (persist != + # score). FE columns now exist; inject all seven onto a clean fixture and + # prove they survive the round-trip deep-equal. + from dataclasses import replace + + # Arrange + original = _load_epc("RdSAP-Schema-21.0.1") + bp0 = original.sap_building_parts[0] + assert bp0.sap_alternative_wall_1 is not None, "fixture must carry an alt wall" + bp0 = replace( + bp0, + sap_alternative_wall_1=replace(bp0.sap_alternative_wall_1, is_sheltered=True), + wall_insulation_thermal_conductivity=35, # Union[int,str] → JSONB int + ) + heating = original.sap_heating + mh0 = replace( + heating.main_heating_details[0], + community_heating_boiler_fuel_type=35, + community_heating_chp_fraction=0.35, + ) + heating = replace( + heating, + main_heating_details=[mh0, *heating.main_heating_details[1:]], + cylinder_volume_measured_l=180, + ) + original = replace( + original, + sap_building_parts=[bp0, *original.sap_building_parts[1:]], + sap_heating=heating, + sap_energy_source=replace(original.sap_energy_source, pv_diverter_present=True), + sap_ventilation=replace( + original.sap_ventilation, air_permeability_ap50_m3_h_m2=7.5 + ), + ) + + # Act + with Session(db_engine) as session: + epc_property_id = EpcPostgresRepository(session).save(original) + session.commit() + with Session(db_engine) as session: + reloaded = EpcPostgresRepository(session).get(epc_property_id) + + # Assert — all seven calculator-read fields survive, deep-equal. + assert reloaded == original + + def test_floor_dimension_heat_loss_flags_round_trip(db_engine: Engine) -> None: # SAP 10.2 §3.3 — `is_exposed_floor` and `is_above_partially_heated_space` # are heat-loss flags on a `SapFloorDimension`: the calculator routes a floor