Model/tests/orchestration/test_modelling_baseline_coherence.py
Khalim Conn-Kowlessar 31edd01999 Flag a modelling baseline that disagrees with the persisted effective 🟥
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:50:39 +00:00

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)