mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-06-08 11:17:27 +00:00
The Plan derives its Valuation Uplift (ADR-0018) from its baseline -> post band jump and works+contingency cost, given one external input — the Property's current market value (a Property Valuation, mostly absent). `Plan.valuation` / `Plan.baseline_epc_rating` are derived like the other headline figures; `PlanModel.from_domain` maps the £ forms to the live plan.valuation_* columns (NULL when no value — the percentage is not persisted on those columns). `Property.current_market_value` is the new optional source; the orchestrator threads it onto the Plan. `run_one` takes a `current_market_value` so the harness can value the uplift, and the sense-check table shows the average % (always) plus the £ forms when known. Sourcing the current market value (upload / default) remains deferred (ADR-0018); it is None throughout until that lands, so the columns stay NULL at scale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.4 KiB
Python
68 lines
2.4 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"
|
|
)
|
|
valuation = plan.valuation
|
|
valuation_line = f" valuation uplift {valuation.average_pct:+.1%}"
|
|
if valuation.average_value is not None and valuation.post_retrofit_value is not None:
|
|
valuation_line += (
|
|
f" ({_money(valuation.average_value)}"
|
|
f" -> {_money(valuation.post_retrofit_value)})"
|
|
)
|
|
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, valuation_line, columns, *rows])
|