mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Normalize RdSAP ventilation mapping on the degraded 17.0/19.0 paths
PRD #1385, ventilation family. The 17.0 and 19.0 API mappers dropped lodged ventilation fields that the reference-complete 17.1/18.0/20.0 paths read: * open_fireplaces_count -> open_chimneys_count: hardcoded 0 on both (understating §2 infiltration). * percent_draughtproofed: omitted on both. * 17.0 additionally passed no sap_ventilation block, so the §2 cascade fell back to NATURAL + its default sheltered_sides=2. Restored from the lodged mechanical_ventilation + built_form via the existing _api_* helpers. Semantic parity verified (not just type-equality): built_form and mechanical_ventilation use the same gov code lists the 17.1/18.0/19.0/20.0/21.x paths already map through these helpers; all 244 RdSAP fixtures resolve with zero UnmappedApiCode, and the strict-coverage guard raises on any future divergence. For the current corpus the 17.0 score-mover is sheltered_sides from built_form (every 17.0 cert lodges mechanical_ventilation=0 -> NATURAL). Adds a regression test asserting both paths carry the fields through from_api_response on real certs. Verification: new test 2 passed; test_mapper_corpus 6002 passed; SAP-accuracy / RealCertExpectation regressions pass; pyright unchanged (39 -> 39, pre-existing). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7dcead2e51
commit
e05f873282
2 changed files with 90 additions and 2 deletions
|
|
@ -596,9 +596,13 @@ class EpcPropertyDataMapper:
|
|||
heated_rooms_count=schema.heated_room_count,
|
||||
wet_rooms_count=0,
|
||||
extensions_count=schema.extensions_count,
|
||||
open_chimneys_count=0,
|
||||
# Both lodged by the schema and read by the 17.1/18.0/20.0 paths.
|
||||
# open_chimneys_count was hardcoded 0 (understating infiltration);
|
||||
# percent_draughtproofed was omitted entirely.
|
||||
open_chimneys_count=schema.open_fireplaces_count,
|
||||
insulated_door_count=schema.insulated_door_count,
|
||||
draughtproofed_door_count=None,
|
||||
percent_draughtproofed=schema.percent_draughtproofed,
|
||||
led_fixed_lighting_bulbs_count=0,
|
||||
cfl_fixed_lighting_bulbs_count=0,
|
||||
incandescent_fixed_lighting_bulbs_count=0,
|
||||
|
|
@ -728,6 +732,19 @@ class EpcPropertyDataMapper:
|
|||
if getattr(bp, "glazed_perimeter", None) is None
|
||||
],
|
||||
sap_conservatory=_api_sap_conservatory(schema.sap_building_parts),
|
||||
# 17.0 lodges `mechanical_ventilation` + `built_form` but passed no
|
||||
# sap_ventilation, so the §2 cascade fell back to NATURAL and its
|
||||
# default sheltered_sides=2. Mirror the 19.0/17.1 path exactly (same
|
||||
# gov code lists — verified in-set across the corpus, strict-coverage
|
||||
# raises on any divergence). For the current corpus the score-mover
|
||||
# is sheltered_sides from built_form (every 17.0 cert lodges
|
||||
# mechanical_ventilation=0 → NATURAL).
|
||||
sap_ventilation=SapVentilation(
|
||||
sheltered_sides=_api_sheltered_sides(schema.built_form),
|
||||
mechanical_ventilation_kind=_api_mechanical_ventilation_kind(
|
||||
schema.mechanical_ventilation
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -1540,9 +1557,13 @@ class EpcPropertyDataMapper:
|
|||
heated_rooms_count=schema.heated_room_count,
|
||||
wet_rooms_count=0,
|
||||
extensions_count=schema.extensions_count,
|
||||
open_chimneys_count=0,
|
||||
# Both lodged by the schema and read by the 17.1/18.0/20.0 paths.
|
||||
# open_chimneys_count was hardcoded 0 (understating infiltration);
|
||||
# percent_draughtproofed was omitted entirely.
|
||||
open_chimneys_count=schema.open_fireplaces_count,
|
||||
insulated_door_count=schema.insulated_door_count,
|
||||
draughtproofed_door_count=None,
|
||||
percent_draughtproofed=schema.percent_draughtproofed,
|
||||
led_fixed_lighting_bulbs_count=0,
|
||||
cfl_fixed_lighting_bulbs_count=0,
|
||||
incandescent_fixed_lighting_bulbs_count=0,
|
||||
|
|
|
|||
67
tests/datatypes/epc/domain/test_mapper_ventilation_parity.py
Normal file
67
tests/datatypes/epc/domain/test_mapper_ventilation_parity.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""Regression: the degraded RdSAP paths (17.0, 19.0) must carry the lodged
|
||||
ventilation fields, like the reference-complete 17.1/18.0/20.0 paths.
|
||||
|
||||
PRD #1385 (mapper normalization). Both paths dropped ventilation lodgements the
|
||||
schema carries and the SAP §2 cascade reads:
|
||||
|
||||
* `open_fireplaces_count` → `open_chimneys_count` — was hardcoded 0 on both,
|
||||
understating infiltration.
|
||||
* `percent_draughtproofed` — was omitted on both.
|
||||
* 17.0 additionally passed no `sap_ventilation` block at all, so the cascade
|
||||
fell back to NATURAL + its default `sheltered_sides=2`. Restored from the
|
||||
lodged `mechanical_ventilation` + `built_form` (same gov code lists the
|
||||
19.0/17.1 paths already use; strict-coverage raises on any divergence). 19.0
|
||||
already carried `sap_ventilation`, so only its two counts are restored here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
|
||||
|
||||
# 17.0 cert: open_fireplaces=1, percent_draughtproofed=100, built_form=2
|
||||
# (Semi-Detached → sheltered_sides=1, i.e. NOT the cascade default of 2),
|
||||
# mechanical_ventilation=0 (natural → kind None).
|
||||
_FIXTURE_17_0 = Path("tests/fixtures/epc_prediction/BD24JG/cert-f326c2524ab3.json")
|
||||
# 19.0 cert: open_fireplaces=1, percent_draughtproofed=100.
|
||||
_FIXTURE_19_0 = Path("tests/fixtures/epc_prediction/RM143YU/cert-720d5771bfd7.json")
|
||||
|
||||
|
||||
def _load(path: Path, schema_type: str) -> dict[str, Any]:
|
||||
data: dict[str, Any] = json.loads(path.read_text())
|
||||
assert data["schema_type"] == schema_type
|
||||
return data
|
||||
|
||||
|
||||
def test_17_0_path_carries_ventilation_lodgements() -> None:
|
||||
epc = EpcPropertyDataMapper.from_api_response(
|
||||
_load(_FIXTURE_17_0, "RdSAP-Schema-17.0")
|
||||
)
|
||||
|
||||
# Counts that were hardcoded 0 / omitted.
|
||||
assert epc.open_chimneys_count == 1
|
||||
assert epc.percent_draughtproofed == 100
|
||||
|
||||
# sap_ventilation block was absent entirely — now built from built_form +
|
||||
# mechanical_ventilation. built_form 2 (Semi-Detached) → sheltered_sides 1,
|
||||
# which differs from the cascade's default of 2 (the real score-mover here).
|
||||
assert epc.sap_ventilation is not None
|
||||
assert epc.sap_ventilation.sheltered_sides == 1
|
||||
# mechanical_ventilation 0 is natural → no mechanical kind.
|
||||
assert epc.sap_ventilation.mechanical_ventilation_kind is None
|
||||
|
||||
|
||||
def test_19_0_path_carries_ventilation_counts() -> None:
|
||||
epc = EpcPropertyDataMapper.from_api_response(
|
||||
_load(_FIXTURE_19_0, "RdSAP-Schema-19.0")
|
||||
)
|
||||
|
||||
# The two counts 19.0 previously dropped.
|
||||
assert epc.open_chimneys_count == 1
|
||||
assert epc.percent_draughtproofed == 100
|
||||
# 19.0 already carried sap_ventilation; confirm it still resolves built_form.
|
||||
assert epc.sap_ventilation is not None
|
||||
assert epc.sap_ventilation.sheltered_sides == 1
|
||||
Loading…
Add table
Reference in a new issue