mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
Two fixes that unblock offline, no-database inspection over an arbitrary EPC dump: - Complete the harness sample catalogue with loft_insulation and solid_floor_insulation — the four fabric generators can emit five Measure Types, but the catalogue priced only three, so an offline run on a property with an uninsulated loft or solid floor raised mid-run. A new test pins the catalogue to cover every generator Measure Type. - Add `run_modelling(epc, ...)` — runs ONLY the Modelling stage (no Ingestion / Baseline), so it needs no lodged recorded-performance / RHI and inspects recommendations on any calculator-scorable EPC. `run_one` (full pipeline) stays for when you want Baseline too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
3.2 KiB
Python
90 lines
3.2 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.sap10_calculator.worksheet._elmhurst_worksheet_000490 import (
|
|
build_epc as _build_uninsulated_cavity_and_floor_epc,
|
|
)
|
|
|
|
# Every Measure Type the four 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",
|
|
"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_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
|