fix(mapper): map cylinder "No Insulation" to insulation_type=None

The Elmhurst §15.1 "Insulated: No Insulation" label was lodged but absent
from `_ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10`, so
`_elmhurst_cylinder_insulation_code` raised UnmappedElmhurstLabel — blocking
the parse of every Solar PV example cert (the solar `before` lodges "No
Insulation"). An uninsulated cylinder has no insulation *type*, so per the
no-misleading-insulation convention it maps to `cylinder_insulation_type =
None` rather than naming a material; the lodged 0 mm thickness carries the
storage-loss signal the SAP 10.2 Table 2 dispatch needs.

Slice 1 of the Solar PV Recommendation Generator (ADR-0026).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-08 09:52:06 +00:00
parent 222704cbc2
commit 545bb8c328
2 changed files with 61 additions and 0 deletions

View file

@ -4868,6 +4868,16 @@ _ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10: Dict[str, int] = {
"Jacket": 2,
}
# Elmhurst §15.1 "Insulated" labels for an uninsulated cylinder. These are
# lodged (not absent), but an uninsulated cylinder has no insulation *type* —
# per the no-misleading-insulation convention it maps to
# `cylinder_insulation_type = None` rather than naming a material. The lodged
# §15.1 "Insulation Thickness" (0 mm) carries the storage-loss signal the
# cascade's SAP 10.2 Table 2 dispatch needs.
_ELMHURST_CYLINDER_NO_INSULATION_LABELS: frozenset[str] = frozenset({
"No Insulation",
})
# Elmhurst §15.0 "Water Heating Fuel Type" labels that route to solid-
# fuel Table 32 codes (Anthracite, House coal, Wood logs/pellets, etc.).
@ -4978,6 +4988,8 @@ def _elmhurst_cylinder_insulation_code(
mapping dict see `_elmhurst_cylinder_size_code` rationale."""
if not cylinder_present or cylinder_insulation_label is None:
return None
if cylinder_insulation_label in _ELMHURST_CYLINDER_NO_INSULATION_LABELS:
return None
code = _ELMHURST_CYLINDER_INSULATION_LABEL_TO_SAP10.get(cylinder_insulation_label)
if code is None:
raise UnmappedElmhurstLabel("cylinder_insulation", cylinder_insulation_label)

View file

@ -0,0 +1,49 @@
"""Mapper boundary: the Elmhurst §15.1 "Insulated" cylinder label.
A cylinder lodged "No Insulation" is an uninsulated cylinder, not a mapper
gap. Per the no-misleading-insulation convention it maps to
`cylinder_insulation_type = None` (don't name an insulation material on an
uninsulated surface) rather than raising `UnmappedElmhurstLabel`. This
unblocks parsing every solar example cert (the solar `before` cert lodges
"No Insulation").
"""
from datatypes.epc.domain.mapper import (
UnmappedElmhurstLabel,
_elmhurst_cylinder_insulation_code, # pyright: ignore[reportPrivateUsage]
)
def test_no_insulation_label_maps_to_none() -> None:
# Arrange
label = "No Insulation"
# Act
code = _elmhurst_cylinder_insulation_code(label, cylinder_present=True)
# Assert
assert code is None
def test_foam_label_still_maps_to_factory_code() -> None:
# Arrange
label = "Foam"
# Act
code = _elmhurst_cylinder_insulation_code(label, cylinder_present=True)
# Assert
assert code == 1
def test_unknown_label_still_raises() -> None:
# Arrange
label = "Spray-on unicorn felt"
# Act / Assert
try:
_elmhurst_cylinder_insulation_code(label, cylinder_present=True)
raised = False
except UnmappedElmhurstLabel:
raised = True
assert raised