mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
PR feedback (dancafc): the SQLModel column was Optional[str], but the domain `SapBuildingPart.wall_insulation_thickness` is Optional[Union[str, int]] — `_api_resolve_wall_insulation_thickness` returns an int mm when the API lodges `wall_insulation_thickness == "measured"` (SAP 10.2 §5.7 / Table 8). The plain str column round-trips that int back as the string "100", corrupting the Table 8 insulated-wall U-value lookup. This column was missed in the round-trip-fidelity §1 JSONB sweep (#1129) — its `Union[str, int]` sibling `roof_insulation_thickness` was converted, but `wall_insulation_thickness` was not, and no 21.0.0/21.0.1 fixture lodges "measured" so the gap stayed latent. Convert to JSONB (matching `roof_insulation_thickness` / `flat_roof_insulation_thickness`), align the column type to Optional[Union[str, int]] (also removes a pyright type-mismatch), record it in the migration doc §1, and add a round-trip guard test asserting an int survives as an int (fails as '100' == 100 on the old str column). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
84 lines
3 KiB
Python
84 lines
3 KiB
Python
"""Persistence round-trip fidelity for EPC Property Data (Slice 1, #1129).
|
|
|
|
The load-bearing risk of the ara_first_run rebuild: an EpcPropertyData mapped to
|
|
the epc_property tables, saved, reloaded and mapped back must reconstruct the
|
|
original object exactly. A failure here is either a missing column (a migration
|
|
the FE repo must make) or a mapper gap — either way we want it to fail loudly,
|
|
inside First Run, rather than be deferred to a later Refresh.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
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 repositories.epc.epc_postgres_repository import EpcPostgresRepository
|
|
|
|
_JSON_SAMPLES = Path(__file__).resolve().parents[3] / "backend/epc_api/json_samples"
|
|
|
|
|
|
def _load_epc(schema_dir: str) -> EpcPropertyData:
|
|
raw: dict[str, Any] = json.loads(
|
|
(_JSON_SAMPLES / schema_dir / "epc.json").read_text()
|
|
)
|
|
return EpcPropertyDataMapper.from_api_response(raw)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"schema_dir",
|
|
["RdSAP-Schema-21.0.0", "RdSAP-Schema-21.0.1"],
|
|
)
|
|
def test_epc_property_data_round_trips(schema_dir: str, db_engine: Engine) -> None:
|
|
# Arrange
|
|
original = _load_epc(schema_dir)
|
|
|
|
# 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
|
|
assert reloaded == original
|
|
|
|
|
|
def test_building_part_wall_insulation_thickness_preserves_int(
|
|
db_engine: Engine,
|
|
) -> None:
|
|
# SAP 10.2 §5.7/Table 8: when the API lodges
|
|
# `wall_insulation_thickness == "measured"`, the mapper resolves the
|
|
# value to an int mm. The `epc_building_part.wall_insulation_thickness`
|
|
# column must therefore preserve int vs str on round-trip (JSONB), like
|
|
# its `roof_insulation_thickness` sibling — a plain str column would
|
|
# round-trip the int 100 back as "100" and corrupt the Table 8 lookup.
|
|
from dataclasses import replace
|
|
|
|
# Arrange — take a green fixture and force the measured-int case.
|
|
original = _load_epc("RdSAP-Schema-21.0.0")
|
|
assert original.sap_building_parts, "fixture must have a building part"
|
|
bp0 = replace(original.sap_building_parts[0], wall_insulation_thickness=100)
|
|
original = replace(
|
|
original,
|
|
sap_building_parts=[bp0, *original.sap_building_parts[1:]],
|
|
)
|
|
|
|
# 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 — the int survives as an int, not the string "100".
|
|
assert reloaded is not None
|
|
value = reloaded.sap_building_parts[0].wall_insulation_thickness
|
|
assert value == 100
|
|
assert isinstance(value, int)
|