Model/tests/harness/test_console.py
Khalim Conn-Kowlessar 7648032d73 feat(modelling): wire solid-wall insulation into the candidate pool
Slice 2e. recommend_solid_wall joins the orchestrator's fabric generator pool
(restrictions default unrestricted until slice 3 sources them); the harness
catalogue + contingencies (26%) gain external_wall_insulation /
internal_wall_insulation. run_modelling on an uninsulated solid-brick dwelling
(baseline SAP 36.6) now selects internal wall insulation into the optimised
package; the catalogue-completeness guard covers both new measure types.
Golden cohort 57/57 still error-free; IWI now fires on a real cohort cert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:15:56 +00:00

111 lines
4.1 KiB
Python

"""The one-property console entrypoint for interactive sense-checking."""
from __future__ import annotations
import dataclasses
import pytest
from datatypes.epc.domain.epc import Epc
from datatypes.epc.domain.epc_property_data import EpcPropertyData
from harness.console import DEFAULT_CATALOGUE, run_modelling, run_one
from repositories.product.product_json_repository import ProductJsonRepository
from tests.domain.modelling._elmhurst_recommendation import (
parse_recommendation_summary,
)
from tests.domain.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
build_epc as _build_uninsulated_cavity_and_floor_epc,
)
# Every Measure Type the fabric generators can emit; the harness catalogue
# must price all of them or an offline run raises mid-pipeline.
_GENERATOR_MEASURE_TYPES = (
"cavity_wall_insulation",
"external_wall_insulation",
"internal_wall_insulation",
"loft_insulation",
"suspended_floor_insulation",
"solid_floor_insulation",
"mechanical_ventilation",
)
def _uninsulated_lodged_epc() -> EpcPropertyData:
epc = _build_uninsulated_cavity_and_floor_epc()
return dataclasses.replace(
epc,
energy_rating_current=57,
current_energy_efficiency_band=Epc.D,
co2_emissions_current=3.0,
energy_consumption_current=300,
)
def test_run_one_returns_a_plan_and_prints_the_table(
capsys: pytest.CaptureFixture[str],
) -> None:
# Arrange
epc: EpcPropertyData = _uninsulated_lodged_epc()
# Act — run one property end-to-end with no database, against the default
# sample catalogue.
plan = run_one(epc, goal_band="C")
# Assert — a multi-measure Plan came back, and its sense-check table printed.
assert len(plan.measures) >= 1
printed: str = capsys.readouterr().out
assert "Plan SAP" in printed
assert "cavity_wall_insulation" in printed
def test_run_modelling_inspects_a_plan_without_baseline_or_lodged_performance() -> None:
# Arrange — the RAW 000490 fixture, with NO lodged recorded-performance, so
# the Baseline stage could not run on it. Modelling re-scores the EPC itself.
epc: EpcPropertyData = _build_uninsulated_cavity_and_floor_epc()
# Act — Modelling only, no Ingestion / Baseline, no database.
plan = run_modelling(epc, goal_band="C", print_table=False)
# Assert — a multi-measure Plan came straight out of Modelling.
assert len(plan.measures) >= 1
def test_run_modelling_recommends_solid_wall_insulation_for_solid_brick() -> None:
# Arrange — an uninsulated solid-brick dwelling (cert 001431 before),
# which has no cavity to fill, so any wall measure must be solid-wall.
epc: EpcPropertyData = parse_recommendation_summary(
"solid_brick_ewi_001431_before.pdf"
)
# Act — Modelling only, no database.
plan = run_modelling(epc, goal_band="C", print_table=False)
# Assert — the solid-wall generator is wired into the candidate pool, so a
# solid-wall Option reaches the optimised package.
measure_types = {measure.measure_type for measure in plan.measures}
assert measure_types & {"external_wall_insulation", "internal_wall_insulation"}
def test_sample_catalogue_prices_every_generator_measure_type() -> None:
# Arrange — the default offline catalogue.
products: ProductJsonRepository = ProductJsonRepository(DEFAULT_CATALOGUE)
# Act / Assert — get() raises if a Measure Type is unpriced, so an offline
# run over arbitrary EPCs never dies on a missing catalogue entry.
for measure_type in _GENERATOR_MEASURE_TYPES:
products.get(measure_type)
def test_run_one_threads_a_current_market_value_onto_the_plan() -> None:
# Arrange
epc: EpcPropertyData = _uninsulated_lodged_epc()
# Act — supply a Property Valuation so the Plan can value the uplift.
plan = run_one(
epc, goal_band="C", current_market_value=250_000.0, print_table=False
)
# Assert — the value reached the Plan, which derives its Valuation Uplift
# from it (the £ amount is 0 here as 000490 stays within band D).
assert plan.current_market_value == 250_000.0
assert plan.valuation.average_value is not None