The PropertyBaselineOrchestrator now reads no Lodged Performance off a predicted
Property's neighbour-synthesised EPC: lodged is None when source_path is
'predicted', so no phantom lodged figure is manufactured. The Effective half is
unchanged. Rebaseliner port + PropertyBaselinePerformance.lodged widen to
Optional; the pristine-cert paths assert non-None (lodged is None only for a
predicted Property, which is always physical_state_changed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CalculatorRebaseliner uses the calculator output as Effective Performance
whenever a Rebaselining trigger fired — pre-SAP10 (a) OR overrides/prediction
moved the physical state (b)/(c) — tagging pre_sap10 / physical_state_changed
/ both. Only a pristine lodged >=10.2 cert keeps its accredited figure (the
sole case the calculator runs purely to validate). Divergence is logged only
in that pristine case. ABC + StubRebaseliner take the new keyword-only flag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Rebaseliner is the assemble-and-score step (ADR-0013 amendment); its
SapResult is the scored picture that Bill Derivation also prices (ADR-0014),
so rebaseline() now returns a RebaselineResult{effective, reason, sap_result}
instead of (Performance, reason). CalculatorRebaseliner sets sap_result on
both branches (the bill prices it whether lodged or calculated figures win);
StubRebaseliner returns sap_result=None (runs no calculator). Orchestrator
unpacks the result; the bill wiring lands in the next slice.
Also refreshes the stale ML-era docstrings in rebaseliner.py to the
assemble-and-score model (the calculator, not ML, is the rebaseliner
mechanism per ADR-0013).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR feedback: the threshold constants were obscure. Rename to state intent —
_SAP10_2_FLOOR -> _MIN_TRUSTED_SAP_VERSION, _SAP_ABS_TOL ->
_MAX_SAP_SCORE_DIVERGENCE, _REL_TOL -> _MAX_RELATIVE_DIVERGENCE — matching
the existing _log_divergence vocabulary, and fold the rationale into the
comments: the calculator emits a continuous SAP score vs the lodged rounded
integer, so a gap up to 0.5 is rounding, beyond it a genuine disagreement
worth recording; CO2/PEUI are not rounded so they get a 1% relative band.
Behaviour unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR feedback: the SapResult -> Performance mapping should be a method, not a
free function you must know exists in the rebaseliner. Put the factory on
the target as `Performance.from_sap_result`, beside its sibling
`lodged_performance` and mirroring `Epc.from_sap_score` (the factory this
mapping already calls).
Not a `SapResult.to_performance()`: that would make the SAP calculator
import `Performance` (a property_baseline type), re-introducing the
engine->consumer coupling removed by the SapCalculator ABC. SapResult is a
TYPE_CHECKING-only import in performance.py (the body only reads attributes),
so the calculator module is not pulled in at runtime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR feedback: annotate locals assigned from a method-call return or
attribute access, even though pyright infers them — the type is visible at
the assignment without chasing the callee. `result: SapResult` and
`sap_version: Optional[float]` in rebaseline(). Local annotations are not
evaluated at runtime, so the TYPE_CHECKING-only SapResult import stands.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR feedback: prefer an abstract base the calculator inherits from over a
structural Protocol. Define `SapCalculator(ABC)` in the calculator package
(the engine owns its own contract) and have `Sap10Calculator` inherit it;
a future methodology is another subclass. Placing the ABC with the engine —
not in property_baseline — keeps the dependency pointing consumer -> engine
(sap10_calculator imports nothing from property_baseline). Consistent with
the repo's existing port convention (FuelRatesRepository(ABC)).
CalculatorRebaseliner keeps its reference to SapCalculator type-only (under
TYPE_CHECKING), so the module still does not import the calculator at
runtime. Test fakes now inherit the ABC since structural conformance no
longer applies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Slice 5a: the promotion. Replaces StubRebaseliner in production and collapses the
shadow runner into the rebaseliner (ADR-0013 amendment).
- CalculatorRebaseliner runs Sap10Calculator on every Property:
* sap_version < 10.2 -> Effective Performance IS the calculator output
(band via Epc.from_sap_score, CO2 kg->t, PEUI rounded), reason "pre_sap10".
* sap_version >= 10.2 -> Effective = lodged (API figures on-target), and the
calculator only logs divergence (SAP>0.5, PEUI/CO2 1%) as a validation signal.
* a calculator raise propagates -> batch aborts (ADR-0012); fix the cert at once.
- Rebaseliner.rebaseline gains property_id (for the divergence log).
- LoggingCalculatorShadow / the calculator_shadow seam removed from the
orchestrator; its divergence-comparison logic now lives in the rebaseliner.
- StubRebaseliner kept (signature updated) for orchestrator/repo unit tests.
The SapResult->EnergyBreakdown adapter + BillDerivation wiring (to populate the
bill block) follow once the appliances/cooking SapResult fields land.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>