Assert a hard zero on the round-trip gate now #1666 has landed 🟩

#1672 (which includes #1666's main_heating_fraction normalisation at the mapper)
is merged into main. The decimal-fraction cohort now maps to integer percent
before save, so the INTEGER column no longer truncates and all 1000 corpus certs
round-trip their SAP to 1e-9. Remove the temporary _is_pre_1666_decimal_fraction
skip so the gate asserts a hard zero over the whole corpus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-07-23 09:30:03 +00:00
parent 4a67857c9a
commit 11ea2c2077

View file

@ -10,15 +10,7 @@ makes the two diverge, so a new persistence blind spot fails here immediately
instead of being found by hand months later (it would have caught
cylinder_heat_loss / room-in-roof / roof-windows / wall_u_value on day one).
Known, documented exception the `main_heating_fraction` DECIMAL cohort. The
gov-EPC API lodges a two-main split as either a decimal fraction (`[0.8, 0.2]`)
or a percent (`[80, 20]`); the `epc_main_heating_detail.main_heating_fraction`
column is INTEGER, so a decimal pair truncates on save (`0.8 -> 1`). The real fix
is #1666 (normalise to percent AT THE MAPPER, before save) which lands on the
SAP-accuracy branch (#1672). Until #1672 is in `main`, those certs cannot
round-trip; they are skipped here via a precise predicate (a non-integer lodged
fraction). Once #1672 merges and this branch rebases, delete `_is_pre_1666_*`
and the skip so the gate asserts a hard zero.
Save+read+rollback per cert keeps the graph off disk (bounded, ~18s / 1000 certs).
"""
from __future__ import annotations
@ -30,7 +22,6 @@ from typing import Any
from sqlalchemy import Engine
from sqlmodel import Session
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
from domain.sap10_calculator.calculator import Sap10Calculator
from repositories.epc.epc_postgres_repository import EpcPostgresRepository
@ -42,17 +33,6 @@ _CORPUS = (
_TOLERANCE = 1e-9
def _is_pre_1666_decimal_fraction(epc: EpcPropertyData) -> bool:
"""True when a lodged main_heating_fraction is non-integer, so the INTEGER
column truncates it on save. #1666 (mapper normalisation) removes this;
remove this predicate + skip once #1672 is in main."""
for main in epc.sap_heating.main_heating_details:
frac = main.main_heating_fraction
if frac is not None and float(frac) != int(frac):
return True
return False
def test_round_trip_preserves_sap_over_the_corpus(db_engine: Engine) -> None:
# Arrange
calc = Sap10Calculator()
@ -60,15 +40,11 @@ def test_round_trip_preserves_sap_over_the_corpus(db_engine: Engine) -> None:
# Act — map -> save -> reload -> re-score every cert, collecting any drift.
diverged: list[str] = []
skipped_pre_1666 = 0
for line in lines:
raw: dict[str, Any] = json.loads(line)
epc = EpcPropertyDataMapper.from_api_response(raw)
if epc is None:
continue
if _is_pre_1666_decimal_fraction(epc):
skipped_pre_1666 += 1
continue
try:
direct = calc.calculate(epc).sap_score_continuous
except Exception:
@ -91,7 +67,6 @@ def test_round_trip_preserves_sap_over_the_corpus(db_engine: Engine) -> None:
# Assert — no field the calculator reads is dropped on the DB round-trip.
assert not diverged, (
f"{len(diverged)} cert(s) lost calculator-read fields on the DB round-trip "
f"({skipped_pre_1666} pre-#1666 decimal-fraction certs skipped):\n"
f"{len(diverged)} cert(s) lost calculator-read fields on the DB round-trip:\n"
+ "\n".join(diverged[:20])
)