mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-27 23:35:01 +00:00
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
"""Modelling and the Baseline stage must agree on what the Property scores now.
|
|
|
|
Per ADR-0002 the persisted Effective Performance is what the app reports as the
|
|
Property's current state, and a Plan's `post_*` figures are read against it. The
|
|
Modelling stage re-scores the Effective EPC itself rather than reading that row,
|
|
so the two can drift apart silently — and when they do, the app subtracts one
|
|
baseline from the other and reports a gain nobody modelled (#1652 / #1655:
|
|
property 749719 showed "+9.65 SAP" on a plan whose own CO2, bill and consumption
|
|
savings were all exactly 0.0).
|
|
|
|
Modelling keeps its own re-score — anchoring the SAP to the persisted integer
|
|
would leave the Plan's SAP disagreeing with its own CO2 / primary energy / bill,
|
|
which all come off the same SapResult. Instead the divergence is made *loud*, so
|
|
a future drift surfaces as an error instead of a phantom gain in a board pack.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
import pytest
|
|
|
|
from datatypes.epc.domain.epc import Epc
|
|
from domain.property_baseline.performance import Performance
|
|
from domain.property_baseline.property_baseline_performance import (
|
|
PropertyBaselinePerformance,
|
|
)
|
|
from orchestration.modelling_orchestrator import (
|
|
_check_baseline_coherence, # pyright: ignore[reportPrivateUsage]
|
|
)
|
|
|
|
|
|
def _persisted(effective_sap: int) -> PropertyBaselinePerformance:
|
|
"""A Baseline Performance whose Effective half scores `effective_sap`."""
|
|
effective = Performance(
|
|
sap_score=effective_sap,
|
|
epc_band=Epc.from_sap_score(effective_sap),
|
|
co2_emissions=2.0,
|
|
primary_energy_intensity=200,
|
|
)
|
|
return PropertyBaselinePerformance(
|
|
lodged=None,
|
|
effective=effective,
|
|
rebaseline_reason="physical_state_changed",
|
|
)
|
|
|
|
|
|
def test_divergence_beyond_rounding_is_logged_as_an_error(
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
# Arrange — the 749719 case: the Baseline stage persisted an Effective SAP of
|
|
# 63, but Modelling scored the same Property at 72.65.
|
|
persisted: Optional[PropertyBaselinePerformance] = _persisted(63)
|
|
|
|
# Act
|
|
with caplog.at_level(logging.ERROR):
|
|
_check_baseline_coherence(749719, 72.65, persisted)
|
|
|
|
# Assert — loud, and it names the Property and both figures.
|
|
assert "749719" in caplog.text
|
|
assert "72.65" in caplog.text
|
|
assert "63" in caplog.text
|
|
assert any(r.levelno == logging.ERROR for r in caplog.records)
|
|
|
|
|
|
def test_a_baseline_agreeing_within_rounding_is_silent(
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
# Arrange — the persisted Effective SAP is the *rounded* score, so a
|
|
# continuous 63.22 against a stored 63 is agreement, not divergence.
|
|
persisted: Optional[PropertyBaselinePerformance] = _persisted(63)
|
|
|
|
# Act
|
|
with caplog.at_level(logging.WARNING):
|
|
_check_baseline_coherence(749719, 63.22, persisted)
|
|
|
|
# Assert
|
|
assert caplog.records == []
|
|
|
|
|
|
def test_a_property_with_no_persisted_baseline_warns_rather_than_failing(
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
# Arrange — the Property never went through the Baseline stage. A data gap
|
|
# should degrade one Property, not abort the batch (ADR-0012 reserves the
|
|
# abort for a load-bearing calculator raise).
|
|
|
|
# Act
|
|
with caplog.at_level(logging.WARNING):
|
|
_check_baseline_coherence(749719, 63.22, None)
|
|
|
|
# Assert — visible, but not an error and not a raise.
|
|
assert "749719" in caplog.text
|
|
assert any(r.levelno == logging.WARNING for r in caplog.records)
|
|
assert not any(r.levelno >= logging.ERROR for r in caplog.records)
|