Model/harness/plan_table.py
Khalim Conn-Kowlessar 9329978374 feat(modelling): sense-check table for a Plan in the DB-less harness
Slice 2. `harness.plan_table.format_plan_table(plan)` renders a Plan as a
plain-text table — one package summary line (baseline SAP/band -> post
SAP/band, CO2 saved, cost of works + contingency, bill saved) and one
line per Plan Measure (signed SAP points, cost, delivered kWh + £
savings). Pure presentation: reads the Plan, computes nothing. The
DB-less First Run test now prints it (visible under `pytest -s`) so the
modelled package can be eyeballed and debugged by hand.

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

61 lines
2.1 KiB
Python

"""Render a Plan as a plain-text sense-check table.
The DB-less inspection harness prints this so the modelled package — its SAP
band transition, cost, and each Plan Measure's attributed SAP / bill impact —
can be eyeballed and debugged by hand. Pure presentation: it reads a `Plan`
domain object and returns a string, computing nothing.
"""
from __future__ import annotations
from typing import Optional
from datatypes.epc.domain.epc import Epc
from domain.modelling.plan import Plan
_KG_PER_TONNE = 1000.0
def _band(sap_continuous: float) -> str:
return Epc.from_sap_score(round(sap_continuous)).value
def _signed_gbp(value: Optional[float]) -> str:
return "n/a" if value is None else f"{value:+,.0f}"
def _money(value: Optional[float]) -> str:
if value is None:
return "n/a"
sign = "-" if value < 0 else ""
return f"{sign}£{abs(value):,.0f}"
def _signed_kwh(value: Optional[float]) -> str:
return "n/a" if value is None else f"{value:+,.0f}"
def format_plan_table(plan: Plan) -> str:
"""A multi-line table: one package summary line, then one line per Plan
Measure (signed so positive is an improvement / a saving)."""
co2_tonnes_saved: float = plan.co2_savings_kg_per_yr / _KG_PER_TONNE
header = (
f"Plan SAP {plan.baseline.sap_continuous:.1f} ({_band(plan.baseline.sap_continuous)})"
f" -> {plan.post_sap_continuous:.1f} ({plan.post_epc_rating.value})"
f" CO2 saved {co2_tonnes_saved:.2f} t/yr"
f" cost £{plan.cost_of_works:,.0f} (+£{plan.contingency_cost:,.0f} cont.)"
f" bill saved {_money(plan.energy_bill_savings)}/yr"
)
columns = (
f" {'measure':<30}{'SAP':>7}{'cost':>10}"
f"{'kWh/yr':>10}{'£/yr':>9}"
)
rows = [
f" {measure.measure_type:<30}"
f"{measure.impact.sap_points:>+7.1f}"
f"{('£' + format(measure.cost.total, ',.0f')):>10}"
f"{_signed_kwh(measure.kwh_savings):>10}"
f"{_signed_gbp(measure.energy_cost_savings):>9}"
for measure in plan.measures
]
return "\n".join([header, columns, *rows])