Merge pull request #1390 from Hestia-Homes/fix/rdsap-mapper-wwhrs-parity

Normalize RdSAP hot-water demand (WWHRS rooms) on the degraded 17.0/19.0 paths
This commit is contained in:
Jun-te Kim 2026-07-01 15:10:37 +01:00 committed by GitHub
commit 6819ca7d89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 79 additions and 0 deletions

View file

@ -653,6 +653,22 @@ class EpcPropertyDataMapper:
),
sap_heating=SapHeating(
instantaneous_wwhrs=InstantaneousWwhrs(),
# ADR-0028: derive HW-demand counts from the lodged bath/shower
# ROOM counts, mirroring the 17.1/18.0/20.0 paths. These were
# dropped (left None), so the water-heating demand fell back to
# defaults instead of the lodged bath/mixer-shower counts.
number_baths=(
schema.sap_heating.instantaneous_wwhrs.rooms_with_bath_and_or_shower
+ schema.sap_heating.instantaneous_wwhrs.rooms_with_bath_and_mixer_shower
if schema.sap_heating.instantaneous_wwhrs is not None
else None
),
mixer_shower_count=(
schema.sap_heating.instantaneous_wwhrs.rooms_with_mixer_shower_no_bath
+ schema.sap_heating.instantaneous_wwhrs.rooms_with_bath_and_mixer_shower
if schema.sap_heating.instantaneous_wwhrs is not None
else None
),
main_heating_details=[
MainHeatingDetail(
has_fghrs=d.has_fghrs == "Y",
@ -1674,6 +1690,22 @@ class EpcPropertyDataMapper:
),
sap_heating=SapHeating(
instantaneous_wwhrs=InstantaneousWwhrs(),
# ADR-0028: derive HW-demand counts from the lodged bath/shower
# ROOM counts, mirroring the 17.1/18.0/20.0 paths. These were
# dropped (left None), so the water-heating demand fell back to
# defaults instead of the lodged bath/mixer-shower counts.
number_baths=(
schema.sap_heating.instantaneous_wwhrs.rooms_with_bath_and_or_shower
+ schema.sap_heating.instantaneous_wwhrs.rooms_with_bath_and_mixer_shower
if schema.sap_heating.instantaneous_wwhrs is not None
else None
),
mixer_shower_count=(
schema.sap_heating.instantaneous_wwhrs.rooms_with_mixer_shower_no_bath
+ schema.sap_heating.instantaneous_wwhrs.rooms_with_bath_and_mixer_shower
if schema.sap_heating.instantaneous_wwhrs is not None
else None
),
main_heating_details=[
MainHeatingDetail(
has_fghrs=d.has_fghrs == "Y",

View file

@ -0,0 +1,47 @@
"""Regression: the degraded RdSAP paths (17.0, 19.0) must derive the hot-water
bath/shower demand counts from the lodged WWHRS room counts, like the
reference-complete 17.1/18.0/20.0 paths.
PRD #1385 (mapper normalization), shower/WWHRS family. Both paths left
`sap_heating.number_baths` / `mixer_shower_count` as None, so the water-heating
demand (`cert_to_inputs`) fell back to defaults. The reference derivation:
number_baths = rooms_with_bath_and_or_shower + rooms_with_bath_and_mixer_shower
mixer_shower_count = rooms_with_mixer_shower_no_bath + rooms_with_bath_and_mixer_shower
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from datatypes.epc.domain.mapper import EpcPropertyDataMapper
# 17.0: rooms bath_or_shower=4, mixer_no_bath=0, bath_and_mixer=4
# → number_baths 8, mixer_shower_count 4.
_FIXTURE_17_0 = Path("tests/fixtures/epc_prediction/W104HL/cert-f96b9b6f471b.json")
# 19.0: rooms bath_or_shower=2, mixer_no_bath=1, bath_and_mixer=1
# → number_baths 3, mixer_shower_count 2.
_FIXTURE_19_0 = Path("tests/fixtures/epc_prediction/LE113PX/cert-ba628e4f9cbf.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_derives_hot_water_demand_from_wwhrs_rooms() -> None:
epc = EpcPropertyDataMapper.from_api_response(
_load(_FIXTURE_17_0, "RdSAP-Schema-17.0")
)
assert epc.sap_heating.number_baths == 8
assert epc.sap_heating.mixer_shower_count == 4
def test_19_0_derives_hot_water_demand_from_wwhrs_rooms() -> None:
epc = EpcPropertyDataMapper.from_api_response(
_load(_FIXTURE_19_0, "RdSAP-Schema-19.0")
)
assert epc.sap_heating.number_baths == 3
assert epc.sap_heating.mixer_shower_count == 2