test: 7 calculator-read EPC fields must round-trip 🟥

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) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-26 18:42:51 +00:00
parent bb4f8577fe
commit 5c4a8d9094
2 changed files with 48 additions and 11 deletions

View file

@ -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)",

View file

@ -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