mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
Persist alternative-wall u_value / thickness / is_basement 🟩
SapAlternativeWall.u_value (heat_transmission.py:1616), .wall_thickness_mm
(:1645) and .is_basement (:1618) were read by the calculator but had no column,
so they dropped on save. Latent today (0 gov-API certs lodge them — live only on
the Elmhurst/site-notes path), but is_basement is the nastiest shape: it does not
null out, it FLIPS meaning (True -> None -> is_basement_wall reads False),
silently switching off the RdSAP §5.17 / Table 23 basement-wall U path.
Add alt_wall_{1,2}_{u_value,thickness_mm,is_basement} to EpcBuildingPartModel,
write in from_domain, reconstruct in _to_alt_wall, and drop the three
_UNPERSISTED_ALLOWLIST entries so the structural guard enforces them. is_basement
is nullable (None "not stated" != False "not a basement"; a NOT NULL DEFAULT
false re-enables the code-6 heuristic — same reasoning as #1661's wall_is_basement).
Companion assessment-model migration: six nullable columns on epc_building_part
(deploy gate). Round-trip + field-coverage guard green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9d2ac59ed2
commit
0890b59b56
3 changed files with 25 additions and 4 deletions
|
|
@ -686,6 +686,12 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
alt_wall_1_thickness_measured: Optional[str] = Field(default=None)
|
||||
alt_wall_1_insulation_thickness: Optional[str] = Field(default=None)
|
||||
alt_wall_1_is_sheltered: Optional[bool] = Field(default=None)
|
||||
# #1665: calculator-read alt-wall fields (heat_transmission.py) with no column.
|
||||
alt_wall_1_u_value: Optional[float] = Field(default=None)
|
||||
alt_wall_1_thickness_mm: Optional[int] = Field(default=None)
|
||||
# Nullable is load-bearing: None ("not stated") != False ("not a basement").
|
||||
# A NOT NULL DEFAULT false re-enables the code-6 basement heuristic (cf #1661).
|
||||
alt_wall_1_is_basement: Optional[bool] = Field(default=None)
|
||||
alt_wall_2_area: Optional[float] = Field(default=None)
|
||||
alt_wall_2_dry_lined: Optional[str] = Field(default=None)
|
||||
alt_wall_2_construction: Optional[int] = Field(default=None)
|
||||
|
|
@ -693,6 +699,9 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
alt_wall_2_thickness_measured: Optional[str] = Field(default=None)
|
||||
alt_wall_2_insulation_thickness: Optional[str] = Field(default=None)
|
||||
alt_wall_2_is_sheltered: Optional[bool] = Field(default=None)
|
||||
alt_wall_2_u_value: Optional[float] = Field(default=None)
|
||||
alt_wall_2_thickness_mm: Optional[int] = Field(default=None)
|
||||
alt_wall_2_is_basement: Optional[bool] = Field(default=None)
|
||||
|
||||
@classmethod
|
||||
def from_domain(
|
||||
|
|
@ -741,6 +750,9 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
aw1.wall_insulation_thickness if aw1 else None
|
||||
),
|
||||
alt_wall_1_is_sheltered=aw1.is_sheltered if aw1 else None,
|
||||
alt_wall_1_u_value=aw1.u_value if aw1 else None,
|
||||
alt_wall_1_thickness_mm=aw1.wall_thickness_mm if aw1 else None,
|
||||
alt_wall_1_is_basement=aw1.is_basement if aw1 else None,
|
||||
alt_wall_2_area=aw2.wall_area if aw2 else None,
|
||||
alt_wall_2_dry_lined=aw2.wall_dry_lined if aw2 else None,
|
||||
alt_wall_2_construction=aw2.wall_construction if aw2 else None,
|
||||
|
|
@ -750,6 +762,9 @@ class EpcBuildingPartModel(SQLModel, table=True):
|
|||
aw2.wall_insulation_thickness if aw2 else None
|
||||
),
|
||||
alt_wall_2_is_sheltered=aw2.is_sheltered if aw2 else None,
|
||||
alt_wall_2_u_value=aw2.u_value if aw2 else None,
|
||||
alt_wall_2_thickness_mm=aw2.wall_thickness_mm if aw2 else None,
|
||||
alt_wall_2_is_basement=aw2.is_basement if aw2 else None,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1044,6 +1044,13 @@ class EpcPostgresRepository(EpcRepository):
|
|||
else bp.alt_wall_2_insulation_thickness
|
||||
)
|
||||
sheltered = bp.alt_wall_1_is_sheltered if n == 1 else bp.alt_wall_2_is_sheltered
|
||||
u_value = bp.alt_wall_1_u_value if n == 1 else bp.alt_wall_2_u_value
|
||||
thickness_mm = (
|
||||
bp.alt_wall_1_thickness_mm if n == 1 else bp.alt_wall_2_thickness_mm
|
||||
)
|
||||
is_basement = (
|
||||
bp.alt_wall_1_is_basement if n == 1 else bp.alt_wall_2_is_basement
|
||||
)
|
||||
return SapAlternativeWall(
|
||||
wall_area=area,
|
||||
wall_dry_lined=_require(dry_lined, f"alt_wall_{n}_dry_lined"),
|
||||
|
|
@ -1058,6 +1065,9 @@ class EpcPostgresRepository(EpcRepository):
|
|||
# Nullable column (added later than the alt wall itself); a row from
|
||||
# before the column existed reads None → the domain default False.
|
||||
is_sheltered=sheltered if sheltered is not None else False,
|
||||
u_value=u_value,
|
||||
wall_thickness_mm=thickness_mm,
|
||||
is_basement=is_basement,
|
||||
)
|
||||
|
||||
@private
|
||||
|
|
|
|||
|
|
@ -45,8 +45,6 @@ _UNPERSISTED_ALLOWLIST: dict[str, str] = {
|
|||
"EpcPropertyData.solar_hw_collector_orientation": "FE column pending — tracked round-trip gap",
|
||||
"EpcPropertyData.solar_hw_collector_pitch_deg": "FE column pending — tracked round-trip gap",
|
||||
"EpcPropertyData.solar_hw_overshading": "FE column pending — tracked round-trip gap",
|
||||
"SapAlternativeWall.u_value": "FE column pending — tracked round-trip gap",
|
||||
"SapAlternativeWall.wall_thickness_mm": "FE column pending — tracked round-trip gap",
|
||||
# Room-in-roof detail: only floor_area + age_band reconstruct from the
|
||||
# inlined columns; the geometry / surface detail awaits an FE child table
|
||||
# (tracked round-trip gap, docs/migrations §2).
|
||||
|
|
@ -58,8 +56,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 the calculator does NOT read (dormant); no FE column.
|
||||
"SapAlternativeWall.is_basement": "dormant — not read by the calculator; no FE column",
|
||||
# --- DELIBERATELY not persisted (#1656). These four ARE read by the
|
||||
# calculator (`heat_transmission.py:994` wall U, `:1068` roof U, `:1118`
|
||||
# floor U, `:955` basement wall) — do NOT re-label them "not read by the
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue