mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-22 08:48:38 +00:00
CONTEXT.md gains Non-Separated / Separated Conservatory glossary terms; ADR-0036 records the mapper-owned §6.1 split + 1:1 persistence + round-trip contract; the migration note lists the five new epc_property columns for the FE schema repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
4.4 KiB
Markdown
78 lines
4.4 KiB
Markdown
---
|
|
Status: accepted
|
|
---
|
|
|
|
# Non-separated conservatory: mapper-owned §6.1 split, persisted 1:1, with round-trip fidelity as the contract
|
|
|
|
Decided in a `/grill-with-docs` session (2026-06-24) while fixing the gov-API
|
|
conservatory path (repro cert 718138, `RdSAP-Schema-19.0`). Extends
|
|
[ADR-0015](0015-mappers-own-cert-normalization.md) (mappers own cert
|
|
normalization) and echoes [ADR-0035](0035-coherent-heating-system-synthesis.md)
|
|
(coherence is a synthesis-time obligation, never a calculator normalisation).
|
|
|
|
## Context
|
|
|
|
The gov register lodges a **Non-Separated Conservatory** (`conservatory_type = 4`)
|
|
as a glazed "building part" carrying only `{floor_area, room_height,
|
|
double_glazed, glazed_perimeter}` — no construction age band, no wall/roof, no
|
|
floor dimensions. Treated as a generic fabric building part it is **mis-scored**
|
|
(its floor heat-loss is counted as a normal part) and **cannot persist** (the
|
|
`epc_building_part` columns `construction_age_band` / `wall_construction` are
|
|
NOT-NULL). The correct RdSAP treatment folds it into the dwelling under §6.1; a
|
|
**Separated Conservatory** (`type 2/3`) is disregarded under §6.2 and lodges no
|
|
glazed part. See CONTEXT.md for both terms.
|
|
|
|
Two placements were available for the §6.1 "split" (detect the glazed part, lift
|
|
it out of the fabric parts, carry it as the dwelling's conservatory):
|
|
|
|
- in the **mapper**, producing a normalized `EpcPropertyData` with
|
|
`sap_conservatory` set and the glazed part excluded from `sap_building_parts`
|
|
(the existing 21.0.1 choice, commit `d501535c`);
|
|
- at the **calculator / persistence-reload boundary**, re-deriving the split
|
|
each time the picture is scored.
|
|
|
|
The production flow is *map → save → reload-from-DB → score*. The mapper split
|
|
alone is invisible to the scored picture, because the reload (`_compose`) did not
|
|
reconstruct `sap_conservatory` and the glazed part was excluded from the
|
|
persisted `sap_building_parts` — so the conservatory was silently dropped on the
|
|
path that actually scores. This is the same latent gap the 21.0.1 fix carried.
|
|
|
|
## Decision
|
|
|
|
1. **The §6.1 split lives in the mapper.** It is cert-shape knowledge, not
|
|
physics (ADR-0015). `EpcPropertyData` — `sap_conservatory` populated, glazed
|
|
part excluded from `sap_building_parts` — is the canonical normalized shape.
|
|
We do **not** re-derive the split in the calculator or at reload; a global
|
|
coherence pass over the assembled picture is rejected for the same reason
|
|
ADR-0035 rejected one for heating.
|
|
2. **Persist the conservatory 1:1 on `epc_property`.** `SapConservatory` is a
|
|
single optional value per dwelling, so it is five nullable columns on
|
|
`epc_property` (alongside the existing scalar conservatory flags), not a child
|
|
table. Save writes them; `_compose` rebuilds `SapConservatory` from them. The
|
|
columns are FE/Drizzle-owned — documented in
|
|
`docs/migrations/epc-property-round-trip-fidelity.md`, applied there, never by
|
|
this repo.
|
|
3. **Round-trip fidelity is the contract that makes the split correct.** Because
|
|
the split lives only in the mapper, the *only* thing guaranteeing the scored
|
|
(reloaded) picture matches the mapped one is that `EpcPropertyData` survives
|
|
`save → get` unchanged. We treat that as an enforced invariant: the
|
|
deep-equality round-trip test (Model#1129) gains a non-separated-conservatory
|
|
case, and a structural field-coverage guard fails if any `EpcPropertyData`
|
|
field is neither reconstructed by `_compose` nor on a documented allow-list —
|
|
turning "latent until a fixture exercises it" into an explicit decision.
|
|
|
|
## Consequences
|
|
|
|
- A future reader sees a conservatory — a "building part" in the source —
|
|
persisted as flat columns on `epc_property` and absent from the dwelling's
|
|
building parts. That is deliberate: it is a §6.1 dwelling attribute, not a
|
|
fabric part.
|
|
- The persistence + guard are schema-version-agnostic, so they close the same
|
|
latent gap for the already-merged 21.0.1 path, not only 19.0.
|
|
- The structural guard documents `sap_roof_windows` and the `solar_hw_collector_*`
|
|
fields as known round-trip gaps (their FE columns are tracked follow-ups), so
|
|
they are visible rather than silently dropped.
|
|
- The per-schema mapper piece (declare the four glazed fields + filter + fold)
|
|
still has to be ported to the remaining gov mappers (17.0/17.1/18.0/20.0.0/
|
|
21.0.0) one validated cert at a time; this ADR covers the shared shape, not
|
|
that rollout.
|