Log a landlord fuel that contradicts the heating archetype's natural fuel 🟩

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Khalim Conn-Kowlessar 2026-06-30 17:16:14 +00:00
parent 648013e05c
commit 79fe54a822
2 changed files with 48 additions and 0 deletions

View file

@ -198,6 +198,16 @@ def _gas_boiler_overlay(code: int) -> HeatingOverlay:
)
def natural_fuel_for(main_heating_value: str) -> Optional[int]:
"""The RdSAP `main_fuel` code a heating archetype unambiguously implies — its
natural fuel (ADR-0041) or None for an unmapped archetype. Exposed so a
plausibility check can compare it against a landlord `main_fuel` override."""
code = _MAIN_HEATING_CODES.get(main_heating_value)
if code is None:
return None
return _natural_fuel_for(code)
def main_heating_overlay_for(
main_heating_value: str, building_part: int
) -> Optional[EpcSimulation]:

View file

@ -28,6 +28,7 @@ values are live vs no-op before any write.
from __future__ import annotations
import logging
from typing import Callable, Optional
from domain.epc.property_overlays.attribute_overlay import (
@ -41,6 +42,7 @@ from domain.epc.property_overlays.glazing_overlay import glazing_overlay_for
from domain.epc.property_overlays.main_fuel_overlay import fuel_overlay_for
from domain.epc.property_overlays.main_heating_system_overlay import (
main_heating_overlay_for,
natural_fuel_for,
)
from domain.epc.property_overlays.water_heating_overlay import (
water_heating_overlay_for,
@ -50,6 +52,8 @@ from domain.epc.property_overlays.wall_type_overlay import wall_overlay_for
from domain.modelling.simulation import EpcSimulation
from repositories.property.property_overrides_reader import ResolvedPropertyOverrides
logger = logging.getLogger(__name__)
# Each override component maps its value (+ building part) to an overlay, or None
# when the value isn't resolvable. Fabric (wall/roof) folds onto building parts;
# property_type / built_form_type are whole-dwelling categorical corrections
@ -90,3 +94,37 @@ def overlays_from(overrides: ResolvedPropertyOverrides) -> list[EpcSimulation]:
if overlay is not None:
overlays.append(overlay)
return overlays
def _override_value(overrides: ResolvedPropertyOverrides, component: str) -> Optional[str]:
for row in overrides.rows:
if row.override_component == component:
return row.override_value
return None
def flag_fuel_mismatch(overrides: ResolvedPropertyOverrides) -> None:
"""Log (not raise) when a landlord `main_fuel` override contradicts the
`main_heating_system` archetype's natural fuel — a plausibility signal (e.g.
a gas fuel on an electric room heater). The override is still honoured (it
wins, ADR-0041); we only record the implausibility. Deferred handling."""
heating_value = _override_value(overrides, "main_heating_system")
fuel_value = _override_value(overrides, "main_fuel")
if heating_value is None or fuel_value is None:
return
natural = natural_fuel_for(heating_value)
fuel_overlay = fuel_overlay_for(fuel_value, 0)
override_fuel = (
fuel_overlay.heating.main_fuel_type
if fuel_overlay is not None and fuel_overlay.heating is not None
else None
)
if natural is None or override_fuel is None:
return
if natural != override_fuel:
logger.warning(
"Landlord main_fuel %r contradicts the natural fuel of heating "
"system %r (override is honoured; flagged for review)",
fuel_value,
heating_value,
)