slice 5: room, door and lighting count features

Ten flat int counts added to the transform — door_count,
habitable/heated/wet/insulated_door counts, extensions, open
chimneys, and the three fixed-lighting bulb counts (CFL/LED/
incandescent). All non-nullable; direct EpcPropertyData field reads.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-05-16 15:03:58 +00:00
parent aa00259b1a
commit e9b4dbbfe5
3 changed files with 139 additions and 12 deletions

View file

@ -24,6 +24,17 @@ def make_minimal_sap10_epc(
energy_consumption_current: Optional[int] = None,
space_heating_kwh: float = 0.0,
water_heating_kwh: float = 0.0,
total_floor_area_m2: float = 70.0,
door_count: int = 0,
habitable_rooms_count: int = 0,
heated_rooms_count: int = 0,
wet_rooms_count: int = 0,
extensions_count: int = 0,
open_chimneys_count: int = 0,
insulated_door_count: int = 0,
cfl_fixed_lighting_bulbs_count: int = 0,
led_fixed_lighting_bulbs_count: int = 0,
incandescent_fixed_lighting_bulbs_count: int = 0,
) -> EpcPropertyData:
"""Construct a minimal valid SAP10 EpcPropertyData with parametrisable targets."""
return EpcPropertyData(
@ -38,7 +49,7 @@ def make_minimal_sap10_epc(
walls=[],
floors=[],
main_heating=[],
door_count=0,
door_count=door_count,
sap_heating=SapHeating(
instantaneous_wwhrs=InstantaneousWwhrs(),
main_heating_details=[],
@ -59,16 +70,16 @@ def make_minimal_sap10_epc(
solar_water_heating=False,
has_hot_water_cylinder=False,
has_fixed_air_conditioning=False,
wet_rooms_count=0,
extensions_count=0,
heated_rooms_count=0,
open_chimneys_count=0,
habitable_rooms_count=0,
insulated_door_count=0,
cfl_fixed_lighting_bulbs_count=0,
led_fixed_lighting_bulbs_count=0,
incandescent_fixed_lighting_bulbs_count=0,
total_floor_area_m2=70.0,
wet_rooms_count=wet_rooms_count,
extensions_count=extensions_count,
heated_rooms_count=heated_rooms_count,
open_chimneys_count=open_chimneys_count,
habitable_rooms_count=habitable_rooms_count,
insulated_door_count=insulated_door_count,
cfl_fixed_lighting_bulbs_count=cfl_fixed_lighting_bulbs_count,
led_fixed_lighting_bulbs_count=led_fixed_lighting_bulbs_count,
incandescent_fixed_lighting_bulbs_count=incandescent_fixed_lighting_bulbs_count,
total_floor_area_m2=total_floor_area_m2,
sap_version=10.2,
energy_rating_current=energy_rating_current,
co2_emissions_current=co2_emissions_current,

View file

@ -117,3 +117,66 @@ def test_to_row_extracts_total_floor_area_m2() -> None:
# Assert
# make_minimal_sap10_epc sets total_floor_area_m2=70.0 by default
assert row["total_floor_area_m2"] == 70.0
_EXPECTED_COUNT_FEATURES: dict[str, type] = {
"door_count": int,
"habitable_rooms_count": int,
"heated_rooms_count": int,
"wet_rooms_count": int,
"extensions_count": int,
"open_chimneys_count": int,
"insulated_door_count": int,
"cfl_fixed_lighting_bulbs_count": int,
"led_fixed_lighting_bulbs_count": int,
"incandescent_fixed_lighting_bulbs_count": int,
}
def test_schema_advertises_count_features() -> None:
# Arrange
transform = EpcMlTransform()
# Act
schema = transform.schema()
# Assert
for feature_name, expected_dtype in _EXPECTED_COUNT_FEATURES.items():
assert feature_name in schema.feature_columns, feature_name
column = schema.feature_columns[feature_name]
assert isinstance(column, ColumnSpec)
assert column.dtype is expected_dtype
assert column.nullable is False
def test_to_row_extracts_count_features() -> None:
# Arrange
epc = make_minimal_sap10_epc(
energy_rating_current=82,
door_count=3,
habitable_rooms_count=5,
heated_rooms_count=4,
wet_rooms_count=1,
extensions_count=1,
open_chimneys_count=0,
insulated_door_count=2,
cfl_fixed_lighting_bulbs_count=0,
led_fixed_lighting_bulbs_count=8,
incandescent_fixed_lighting_bulbs_count=2,
)
transform = EpcMlTransform()
# Act
row = transform.to_row(epc)
# Assert
assert row["door_count"] == 3
assert row["habitable_rooms_count"] == 5
assert row["heated_rooms_count"] == 4
assert row["wet_rooms_count"] == 1
assert row["extensions_count"] == 1
assert row["open_chimneys_count"] == 0
assert row["insulated_door_count"] == 2
assert row["cfl_fixed_lighting_bulbs_count"] == 0
assert row["led_fixed_lighting_bulbs_count"] == 8
assert row["incandescent_fixed_lighting_bulbs_count"] == 2

View file

@ -19,11 +19,53 @@ from domain.ml.ucl import apply_ucl_correction
_FEATURE_COLUMNS: dict[str, ColumnSpec] = {
# Geometry
"total_floor_area_m2": ColumnSpec(
dtype=float,
nullable=False,
description="Total floor area in square metres, from `total_floor_area`.",
),
# Counts — directly populated by all SAP10 EPCs
"door_count": ColumnSpec(
dtype=int, nullable=False, description="Number of external doors."
),
"habitable_rooms_count": ColumnSpec(
dtype=int, nullable=False, description="Number of habitable rooms."
),
"heated_rooms_count": ColumnSpec(
dtype=int, nullable=False, description="Number of heated rooms."
),
"wet_rooms_count": ColumnSpec(
dtype=int, nullable=False, description="Number of wet rooms (bathrooms / WCs)."
),
"extensions_count": ColumnSpec(
dtype=int,
nullable=False,
description="Number of extensions beyond the main dwelling.",
),
"open_chimneys_count": ColumnSpec(
dtype=int, nullable=False, description="Number of open chimneys."
),
"insulated_door_count": ColumnSpec(
dtype=int,
nullable=False,
description="Number of external doors classed as insulated.",
),
"cfl_fixed_lighting_bulbs_count": ColumnSpec(
dtype=int,
nullable=False,
description="Number of CFL bulbs in fixed lighting outlets.",
),
"led_fixed_lighting_bulbs_count": ColumnSpec(
dtype=int,
nullable=False,
description="Number of LED bulbs in fixed lighting outlets.",
),
"incandescent_fixed_lighting_bulbs_count": ColumnSpec(
dtype=int,
nullable=False,
description="Number of incandescent bulbs in fixed lighting outlets.",
),
}
@ -99,8 +141,19 @@ class EpcMlTransform:
"""
rhi = epc.renewable_heat_incentive
return {
# Features
# Features — geometry
"total_floor_area_m2": epc.total_floor_area_m2,
# Features — counts
"door_count": epc.door_count,
"habitable_rooms_count": epc.habitable_rooms_count,
"heated_rooms_count": epc.heated_rooms_count,
"wet_rooms_count": epc.wet_rooms_count,
"extensions_count": epc.extensions_count,
"open_chimneys_count": epc.open_chimneys_count,
"insulated_door_count": epc.insulated_door_count,
"cfl_fixed_lighting_bulbs_count": epc.cfl_fixed_lighting_bulbs_count,
"led_fixed_lighting_bulbs_count": epc.led_fixed_lighting_bulbs_count,
"incandescent_fixed_lighting_bulbs_count": epc.incandescent_fixed_lighting_bulbs_count,
# Targets
"sap_score": epc.energy_rating_current,
"co2_emissions": epc.co2_emissions_current,