Model/tests/domain/epc/test_main_fuel_guard.py
Jun-te Kim 5330bc20d9 Add individual wood-logs main-fuel archetype (API 6 / RdSAP Table 32 code 20)
MainFuelType had no individual wood-logs member — only "biomass (community)" —
so the LLM classifier funnelled "Solid Fuel: Wood Logs" into the community fuel,
inventing a community heat network the dwelling isn't on (and mislabelling the
connection). main_fuel had no deterministic guard at all, so nothing caught it.

Verified against domain/sap10_calculator/docs/specs: RdSAP 10 Specification
Table 32 lists "wood logs" as a solid fuel (code 20, 0.028 kgCO2e/kWh); the
calculator's input scheme (the gov EPC API fuel enum) codes it 6 -> Table 32 20
(sap_efficiencies._API_TO_TABLE32), and water_heating_overlay already pins the
same fuel to 6. So _FUEL_CODES["wood logs"] = 6 is confirmed, not guessed.

Adds MainFuelType.WOOD_LOGS + the _FUEL_CODES entry, a main_fuel_guard mirroring
water_heating_guard (claims the "wood log" token; dual fuel keeps its own member
since it has no "wood log" substring), and wires main_fuel through a
GuardedColumnClassifier so the live path is deterministic.

Applied the scoped backfill to portfolio 796 (Hyde): 21 rows off
"biomass (community)" -> "wood logs". property_overrides (TEXT) only; the
classifier-cache pgEnum member is deferred to the FE Drizzle migration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:00:24 +00:00

43 lines
1.4 KiB
Python

from __future__ import annotations
import pytest
from domain.epc.property_overrides.main_fuel_guard import main_fuel_guard
from domain.epc.property_overrides.main_fuel_type import MainFuelType
def test_guard_resolves_individual_wood_logs() -> None:
# Arrange — the bug: with no individual wood-logs member, the LLM funnelled a
# solid-fuel wood-logs dwelling into "biomass (community)" (inventing a
# community heat network it isn't on).
description = "Solid Fuel: Wood Logs"
# Act
result = main_fuel_guard(description)
# Assert — an individual wood-logs fuel, never community biomass.
assert result is MainFuelType.WOOD_LOGS
@pytest.mark.parametrize(
"description",
[
# Fuels the LLM already classifies correctly — the guard only rescues the
# wood-logs gap and leaves the rest to the fallback classifier.
"Gas: Mains Gas",
"Electricity: Electricity",
"Oil and Liquid Fuels: Oil",
# Dual fuel contains "wood" but not "wood log" — it has its own member (10)
# and must not be stolen by the wood-logs rule.
"Solid Fuel: Dual Fuel: Mineral and Wood",
# Genuine community biomass keeps its community member.
"Other Heat: Biomass (Community)",
"",
],
)
def test_guard_defers_other_fuels_to_the_llm(description: str) -> None:
# Act
result = main_fuel_guard(description)
# Assert
assert result is None