mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-30 13:10:47 +00:00
Merge pull request #1334 from Hestia-Homes/feat/rdsap-19-21-ventilation
fix(epc-mapper): construct sap_ventilation in RdSAP 19.0 / 21.0.0 mappers
This commit is contained in:
commit
56b365f488
3 changed files with 76 additions and 20 deletions
|
|
@ -1698,6 +1698,19 @@ class EpcPropertyDataMapper:
|
|||
if getattr(bp, "glazed_perimeter", None) is None
|
||||
],
|
||||
sap_conservatory=_api_sap_conservatory(schema.sap_building_parts),
|
||||
# ADR-0037 follow-up: 19.0 previously passed no `sap_ventilation=`,
|
||||
# dropping the whole §2 block. The schema only lodges the top-level
|
||||
# `mechanical_ventilation` enum + `built_form` (no fan/flue/vent
|
||||
# counts — RdSAP uses Table-5 age defaults), so mirror just those
|
||||
# two fields from 21.0.1. An MEV/MVHR cert that defaulted to NATURAL
|
||||
# under-stated its ventilation heat loss; `sheltered_sides` (§S5)
|
||||
# avoids the cascade's shelter default of 2.
|
||||
sap_ventilation=SapVentilation(
|
||||
sheltered_sides=_api_sheltered_sides(schema.built_form),
|
||||
mechanical_ventilation_kind=_api_mechanical_ventilation_kind(
|
||||
schema.mechanical_ventilation
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -2274,6 +2287,19 @@ class EpcPropertyDataMapper:
|
|||
else None
|
||||
),
|
||||
),
|
||||
# ADR-0037 follow-up: 21.0.0 previously passed no `sap_ventilation=`,
|
||||
# dropping the whole §2 block. Unlike 21.0.1 it does not lodge the
|
||||
# fan/flue/vent counts, so mirror only the two fields it does carry:
|
||||
# the top-level `mechanical_ventilation` enum (→ §2 MV-kind dispatch;
|
||||
# an MEV/MVHR cert otherwise defaulted to NATURAL and under-stated
|
||||
# ventilation heat loss) and `sheltered_sides` from built_form (§S5,
|
||||
# vs the cascade's shelter default of 2).
|
||||
sap_ventilation=SapVentilation(
|
||||
sheltered_sides=_api_sheltered_sides(schema.built_form),
|
||||
mechanical_ventilation_kind=_api_mechanical_ventilation_kind(
|
||||
schema.mechanical_ventilation
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -2710,22 +2710,48 @@ def test_rdsap_mappers_carry_main_heating_controls_display(
|
|||
(RdSapSchema17_1, EpcPropertyDataMapper.from_rdsap_schema_17_1, "17_1.json"),
|
||||
(RdSapSchema18_0, EpcPropertyDataMapper.from_rdsap_schema_18_0, "18_0.json"),
|
||||
(RdSapSchema20_0_0, EpcPropertyDataMapper.from_rdsap_schema_20_0_0, "20_0_0.json"),
|
||||
(RdSapSchema19_0, EpcPropertyDataMapper.from_rdsap_schema_19_0, "19_0.json"),
|
||||
(RdSapSchema21_0_0, EpcPropertyDataMapper.from_rdsap_schema_21_0_0, "21_0_0.json"),
|
||||
],
|
||||
)
|
||||
def test_rdsap_mappers_map_mechanical_ventilation_kind(
|
||||
schema_cls: Any, mapper: Any, fixture: str
|
||||
) -> None:
|
||||
# Calc-facing: an MVHR cert (mechanical_ventilation=4) must reach the §2
|
||||
# ventilation cascade as MVHR, not be silently treated as natural. These
|
||||
# three build a bare SapVentilation(sheltered_sides=…) and dropped the
|
||||
# top-level mechanical_ventilation; 17.0 + 21.0.1 + full-SAP already map it
|
||||
# (via _sap_17_1_ventilation / their own wiring). 19.0 + 21.0.0 set no
|
||||
# sap_ventilation at all — a bigger, separate gap (see followups). Natural
|
||||
# ventilation cascade as MVHR, not be silently treated as natural. 17.1 /
|
||||
# 18.0 / 20.0.0 built a bare SapVentilation(sheltered_sides=…) and dropped
|
||||
# the top-level mechanical_ventilation (fixed in #1333); 19.0 + 21.0.0 set
|
||||
# no sap_ventilation at all, dropping the whole block (fixed here — the
|
||||
# remaining ADR-0037 ventilation follow-up). 17.0 + 21.0.1 + full-SAP
|
||||
# already map it (via _sap_17_1_ventilation / their own wiring). Natural
|
||||
# certs (code 0/5 → None) are unchanged, so the fix only moves genuine
|
||||
# MEV/MVHR certs (ADR-0037 follow-up).
|
||||
# MEV/MVHR certs.
|
||||
data = load(fixture)
|
||||
data["mechanical_ventilation"] = 4
|
||||
|
||||
result = mapper(from_dict(schema_cls, data))
|
||||
|
||||
assert result.sap_ventilation.mechanical_ventilation_kind == "MVHR"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"schema_cls, mapper, fixture",
|
||||
[
|
||||
(RdSapSchema19_0, EpcPropertyDataMapper.from_rdsap_schema_19_0, "19_0.json"),
|
||||
(RdSapSchema21_0_0, EpcPropertyDataMapper.from_rdsap_schema_21_0_0, "21_0_0.json"),
|
||||
],
|
||||
)
|
||||
def test_rdsap_19_0_21_0_0_construct_sap_ventilation_block(
|
||||
schema_cls: Any, mapper: Any, fixture: str
|
||||
) -> None:
|
||||
# 19.0 + 21.0.0 previously passed no `sap_ventilation=` at all → the
|
||||
# EpcPropertyData default empty SapVentilation, dropping the entire §2
|
||||
# block (sheltered_sides + kind). Mirroring 21.0.1, they now construct it.
|
||||
# `sheltered_sides` derives from built_form per §S5 (both fixtures lodge
|
||||
# built_form=2 Semi-Detached → 1 sheltered side); a missing/empty block
|
||||
# would leave it None and over-count shelter via the cascade default of 2.
|
||||
schema = from_dict(schema_cls, load(fixture))
|
||||
|
||||
result = mapper(schema)
|
||||
|
||||
assert result.sap_ventilation.sheltered_sides == 1
|
||||
|
|
|
|||
|
|
@ -12,12 +12,15 @@ Each is **separate from** the full-SAP fix (`fix/baseline-downgrades`).
|
|||
`mechanical_ventilation_kind`. ✅
|
||||
- `21_0_1` — rich inline `SapVentilation(...)` incl. the kind. ✅
|
||||
- **`17_1` / `18_0` / `20_0_0`** — built `SapVentilation(sheltered_sides=…)` and
|
||||
**dropped** the kind. **Fixed in this PR** (mirror `_api_mechanical_ventilation_kind`).
|
||||
- **`19_0` / `21_0_0`** — set **no `sap_ventilation` at all** → the dataclass
|
||||
default empty object. They drop the **entire** ventilation block (sheltered
|
||||
sides + kind + everything), not just the kind. **Still open** — a bigger,
|
||||
separate consistency fix (give them a proper `sap_ventilation` construction,
|
||||
mirroring 21.0.1), not a one-liner.
|
||||
**dropped** the kind. **Fixed in #1333** (mirror `_api_mechanical_ventilation_kind`).
|
||||
- **`19_0` / `21_0_0`** — set **no `sap_ventilation` at all** → the
|
||||
`EpcPropertyData` default (`None`). They dropped the **entire** ventilation
|
||||
block (sheltered sides + kind), not just the kind. **Fixed in
|
||||
`feat/rdsap-19-21-ventilation`**: both now construct `SapVentilation(...)`
|
||||
mirroring 21.0.1, but only for the fields each schema lodges — the top-level
|
||||
`mechanical_ventilation` enum (→ kind) and `sheltered_sides` (from
|
||||
`built_form`). Neither lodges the fan/flue/vent counts, so those stay unset
|
||||
(RdSAP Table-5 age defaults — see counts note below).
|
||||
|
||||
Either way, an MEV/MVHR cert (`mechanical_ventilation ≠ 0`) is treated as
|
||||
**natural** by the affected mappers — wrong §2 ventilation cascade (and heat
|
||||
|
|
@ -34,14 +37,15 @@ The granular **counts** (fans/flues/vents) are *not* a bug: older RdSAP open-dat
|
|||
certs don't lodge them, and the calc correctly uses RdSAP Table-5 age defaults.
|
||||
`percent_draughtproofed` is mapped (top-level) and read by the calc.
|
||||
|
||||
**Remaining fix (19.0 / 21.0.0):** give them a proper `sap_ventilation`
|
||||
construction mirroring 21.0.1. **Calc-facing → validate** with the RdSAP-21.0.1
|
||||
corpus (must hold 73.3% / MAE 0.774) plus an **Elmhurst-anchored MEV/MVHR
|
||||
`RealCertExpectation`** (the corpus is natural-vent-dominated, so the kind change
|
||||
isn't exercised by it). Quantify blast radius: count older-RdSAP certs with
|
||||
`mechanical_ventilation ≠ 0`. The 17.1/18.0/20.0.0 fix in this PR is guarded by a
|
||||
mapper-level MVHR test + the corpus/mapper-corpus staying green, with the Elmhurst
|
||||
MEV/MVHR anchor as the SAP-accuracy fast-follow.
|
||||
**19.0 / 21.0.0 fix (done, `feat/rdsap-19-21-ventilation`):** both build
|
||||
`SapVentilation(sheltered_sides=…, mechanical_ventilation_kind=…)` mirroring
|
||||
21.0.1. Guarded by the extended mapper-level MVHR test + a `sheltered_sides`
|
||||
regression, with the corpus (held 73.3% / MAE 0.774 — it's 21.0.1-only so the
|
||||
change isn't exercised by it) and the 6002-case mapper-corpus staying green.
|
||||
**Outstanding SAP-accuracy fast-follow:** an **Elmhurst-anchored MEV/MVHR
|
||||
`RealCertExpectation`** (the corpus is natural-vent-dominated, so the kind
|
||||
change isn't exercised by it). **Blast radius (still to quantify):** count
|
||||
portfolio-796 certs on schema 19.0 / 21.0.0 with `mechanical_ventilation ≠ 0`.
|
||||
|
||||
## 2. FE "Main Fuel: Unknown" is FE-side, not a Model mapper gap
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue