mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
feat(db): persist rafter_insulation_thickness and wall_is_basement
The Model repo reads both fields from the gov-EPC API but neither had a column here, so both were mapped and then silently discarded on save -- inert for any dwelling read back from Postgres. rafter_insulation_thickness: property 749719 lodges "250mm", which bills the roof on RdSAP 10 Table 16 col (2) at U 0.23. Dropped on the DB round-trip it falls to the Table 18 col (2) unknown-thickness default of 2.30 -- the spec's *uninsulated* value, a 10x error: quantity | lodged | dropped | with field SAP | 73 (band C) | 63.22 | 72.65 primary energy kWh/m2/yr | 212 | 327.9 | 212.3 CO2 t/yr | 1.5 | 2.40 | 1.54 It is jsonb, not text, to match the sibling thickness columns: the value is Union[str, int] -- "250mm" from the gov API, int mm from Site Notes -- and the Python round-trip test asserts int-vs-str survives exactly. wall_is_basement: not a lodged U-value -- it selects WHICH RdSAP default applies (RdSAP 10 5.17 / Table 23). Nullable is load-bearing, so it is deliberately NOT notNull().default(false). Three states are distinct: null "not stated" falls back to the gov-API code-6 heuristic, false "explicitly system-built" suppresses it, true is a basement wall. Code 6 is canonically system-built and the Elmhurst site-notes mapper sets false precisely to defeat the heuristic, so collapsing false to null INVERTS the determination -- a system-built wall bills as a basement wall and, via has_basement, drags the whole ground floor onto the Table 23 basement-floor U-value. Rare (54 of 67k building parts in the 2026 sweep) but silent and directional. Deliberately NOT persisted: wall_u_value, roof_u_value, floor_u_value. Those are full-SAP artefacts. We re-model every dwelling *as* an RdSAP assessment, which derives U-values from the construction-default cascade rather than honouring an assessor's measured value, so persisting them would make the re-model a hybrid. Corpus check across 1,000 RdSAP-21.0.1 certs: roof_u_value 0, floor_u_value 0, wall_u_value 4 (0.4%). Both columns nullable, additive, no backfill -- neither ever existed, so there is nothing to restore. Affected properties must be re-ingested from the gov-EPC API before their scores move. Deploy ordering: this migration must land BEFORE the Model-side backend deploys. The Python SQLModel now selects both columns, so without them every EPC read fails outright with "column epc_building_part.rafter_insulation_thickness does not exist" -- not a graceful degradation. Refs Hestia-Homes/Model#1661 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
fa4f3f3ab0
commit
4f41570da4
4 changed files with 13156 additions and 0 deletions
2
src/app/db/migrations/0275_opposite_ronan.sql
Normal file
2
src/app/db/migrations/0275_opposite_ronan.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE "epc_building_part" ADD COLUMN "rafter_insulation_thickness" jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "epc_building_part" ADD COLUMN "wall_is_basement" boolean;
|
||||
13129
src/app/db/migrations/meta/0275_snapshot.json
Normal file
13129
src/app/db/migrations/meta/0275_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1919,6 +1919,13 @@
|
|||
"when": 1784631274000,
|
||||
"tag": "0274_seed_project_type_and_workstream",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 275,
|
||||
"version": "7",
|
||||
"when": 1784656804281,
|
||||
"tag": "0275_opposite_ronan",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -778,10 +778,28 @@ export const epcBuildingPart = pgTable(
|
|||
roofConstruction: integer("roof_construction"),
|
||||
roofInsulationLocation: jsonb("roof_insulation_location"),
|
||||
roofInsulationThickness: jsonb("roof_insulation_thickness"),
|
||||
// RdSAP 10 §5.11.2 — a roof insulated AT RAFTERS lodges its thickness here,
|
||||
// NOT in roof_insulation_thickness (which stays null for rafter roofs).
|
||||
// jsonb (not text) to match the sibling thickness columns: the value is
|
||||
// Union[str, int] — "250mm" from the gov API, int mm from Site Notes.
|
||||
rafterInsulationThickness: jsonb("rafter_insulation_thickness"),
|
||||
|
||||
roofConstructionType: text("roof_construction_type"),
|
||||
curtainWallAge: text("curtain_wall_age"),
|
||||
|
||||
// RdSAP 10 §5.17 / Table 23 — selects the basement-wall U-value column for the
|
||||
// part's primary wall. NOT a lodged U-value: it picks WHICH RdSAP default
|
||||
// applies. Nullable is load-bearing — do NOT add .notNull().default(false).
|
||||
// Three distinct states:
|
||||
// null → "not stated" → fall back to the gov-API code-6 heuristic
|
||||
// false → "explicitly system-built" → do NOT apply the heuristic
|
||||
// true → basement wall
|
||||
// RdSAP code 6 is canonically system-built, and the Elmhurst site-notes mapper
|
||||
// sets false precisely to defeat the heuristic. Collapsing false to null INVERTS
|
||||
// the result: a system-built wall bills as a basement wall, and via has_basement
|
||||
// drags the whole ground floor onto the Table 23 basement-floor U-value.
|
||||
wallIsBasement: boolean("wall_is_basement"),
|
||||
|
||||
// Room in roof (inlined)
|
||||
roomInRoofFloorArea: real("room_in_roof_floor_area"),
|
||||
roomInRoofConstructionAgeBand: text("room_in_roof_construction_age_band"),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue