The context sets, the packaged knowledge bases and the example bundles are replaced by one fictitious example set about IT operations in an invented organisation: three context sets (serverrom-2027, driftsavtale-2027 and the two-base drift-og-avtale-2027), two synthetic knowledge bases under src/portfolio_optimiser/data/kunnskapsbaser and two example bundles under src/portfolio_optimiser/data/bundles. Numbers, codes and structural values in tests and fixtures are kept; names, ids and wording change. Dated measurement documents that only recorded runs on the replaced material are deleted. Gate figures measured on the new set are not comparable with earlier ones. The exclusion gate from the previous commit is green: 0 tracked files hit outside the shared/ subtree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
216 lines
10 KiB
Python
216 lines
10 KiB
Python
"""Kø-(p) LOAD-BEARING: money is quantized to øre in ONE order, from ONE source.
|
|
|
|
Two orders existed. ``ledger`` quantizes EVERY realized candidate to integer øre and sums the
|
|
ints (``ledger.py`` documents ``amount_ore`` as "exact, order-independent sums"); ``run.py``'s
|
|
goal baselines summed ``Project.total_cost`` FLOATS across items and projects and quantized the
|
|
total ONCE. The two met at exactly one place — ``_goal_limit_if_reached``, where a percent goal
|
|
compares the ledger's quantize-first ``observed_ore`` against a threshold derived from the
|
|
sum-first baseline. A comparison whose two sides are computed in different orders decides whether
|
|
a portfolio pass stops early, so the divergence is not cosmetic.
|
|
|
|
DECISION (Kø-(p)): **quantize each cost line, then sum integers.** Each ``CostItem`` IS a money
|
|
amount ("One cost line in a project's estimate"), and S4.0 made per-line ``quantity``/``unit_cost``
|
|
the validator's ground truth — a real amount has a real number of øre. Float summation is also
|
|
order-dependent, which is the nondeterminism class ``_wave_snapshot`` already eliminates elsewhere.
|
|
|
|
The fixture makes the two orders disagree by construction: three lines of ``60000.005`` NOK each.
|
|
Quantize-first gives ``6000001`` øre per line -> ``18000003``; the float sum drifts to
|
|
``180000.01499999998`` and quantize-last gives ``18000001``. A ledger observation of ``18000002``
|
|
øre sits STRICTLY BETWEEN the two thresholds, so a 100 % portfolio goal stops the pass under the
|
|
old order and does not under the new one — RED in either detach direction.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from portfolio_optimiser import ledger as ledger_mod
|
|
from portfolio_optimiser import run as run_mod
|
|
from portfolio_optimiser.contracts import GoalConfig, GoalContract
|
|
from portfolio_optimiser.ledger import SavingsLedger, realize
|
|
from portfolio_optimiser.reference_domain import CostItem, Project
|
|
from portfolio_optimiser.run import run_portfolio
|
|
from portfolio_optimiser.verdicts import ProposalFeatures, capture_verdict
|
|
|
|
# Three lines whose per-line øre value ends in a half øre (60000.005 NOK = 6000000.5 øre).
|
|
_HALF_ORE_UNIT_COST = 60000.005
|
|
_QUANTIZE_FIRST_ORE = 18_000_003 # 3 x ROUND_HALF_UP(6000000.5) = 3 x 6000001
|
|
_SUM_FIRST_ORE = 18_000_001 # ROUND_HALF_UP(100 x 180000.01499999998)
|
|
_OBSERVED_ORE = 18_000_002 # strictly between the two -> discriminates the orders
|
|
_OBSERVED_NOK = 180_000.02
|
|
|
|
# Whole-øre control lines: the two orders agree, so the outcome must NOT flip.
|
|
_WHOLE_ORE_UNIT_COST = 60_000.00
|
|
|
|
# A proposal reconciling with the fixture's first cost line (S4.0 baseline gate). Degenerate
|
|
# Monte Carlo: P90 = 0.30 x 60000.005 = 18000.0015 >= claimed 15000 -> validates.
|
|
_ALIGNED_REPLY = (
|
|
'{"measure":"LED-retrofit av kontorbelysning","affected_items":'
|
|
'[{"code":"ORE-A","quantity":1.0,"unit_cost":60000.005}],"claimed_saving_nok":15000}'
|
|
)
|
|
|
|
|
|
def _make_docs(tmp_path) -> str:
|
|
"""A tmp docs folder with citable content, so the reference path retrieval is non-empty."""
|
|
d = tmp_path / "ore-docs"
|
|
d.mkdir()
|
|
(d / "cost.txt").write_text(
|
|
"Lighting retrofit reduced the electricity cost on the office stretch.", encoding="utf-8"
|
|
)
|
|
return str(d)
|
|
|
|
|
|
def _project(tmp_path, *, unit_cost: float) -> Project:
|
|
return Project(
|
|
id="ORE-P",
|
|
name="Øre project",
|
|
description="a project whose cost lines expose the quantization order",
|
|
currency="NOK",
|
|
cost_items=tuple(
|
|
CostItem(
|
|
code=code,
|
|
description=f"line {code}",
|
|
quantity=1.0,
|
|
unit="stk",
|
|
unit_cost=unit_cost,
|
|
)
|
|
for code in ("ORE-A", "ORE-B", "ORE-C")
|
|
),
|
|
docs_dir=_make_docs(tmp_path),
|
|
verdict_input={"decision": "approved", "rationale": "reviewed (sim)"},
|
|
bundle_dir=None,
|
|
verdict_dir=None,
|
|
)
|
|
|
|
|
|
def _ledger_observing(amount_nok: float) -> SavingsLedger:
|
|
"""A ledger holding ONE realized entry worth ``amount_nok`` — the quantize-first side of the
|
|
comparison, built through the production ``realize`` path rather than a hand-made entry."""
|
|
ledger = SavingsLedger(entries=[])
|
|
features = ProposalFeatures(
|
|
affected_codes=frozenset({"ORE-A"}),
|
|
measure_type="lighting retrofit",
|
|
claimed_saving_nok=amount_nok,
|
|
)
|
|
realize(
|
|
ledger,
|
|
features,
|
|
capture_verdict(features, "approved", "realized out of band"),
|
|
project_id="ORE-P",
|
|
dimension="kostnad",
|
|
approver="persona",
|
|
experiment="ko-p",
|
|
timestamp="2026-08-03T00:00:00Z",
|
|
)
|
|
return ledger
|
|
|
|
|
|
def test_the_fixture_actually_discriminates_the_two_orders() -> None:
|
|
"""The premise itself, MEASURED — not assumed. If these constants ever stop disagreeing the
|
|
load-bearing test below would pass vacuously, so the disagreement is asserted first."""
|
|
lines = [1.0 * _HALF_ORE_UNIT_COST] * 3
|
|
assert sum(ledger_mod.to_ore(x) for x in lines) == _QUANTIZE_FIRST_ORE
|
|
assert ledger_mod.to_ore(sum(lines, 0.0)) == _SUM_FIRST_ORE
|
|
assert _SUM_FIRST_ORE < _OBSERVED_ORE < _QUANTIZE_FIRST_ORE
|
|
assert ledger_mod.to_ore(_OBSERVED_NOK) == _OBSERVED_ORE
|
|
|
|
|
|
def test_nok_to_ore_conversion_has_exactly_one_source() -> None:
|
|
"""ONE source, not two copies that drift (the S4.0 ``REPLIES`` precedent). ``run`` must use
|
|
``ledger``'s conversion by IDENTITY — a re-implementation that happens to agree today is the
|
|
defect class, not the fix. Reintroduce a private copy in ``run.py`` -> RED."""
|
|
assert run_mod.to_ore is ledger_mod.to_ore, (
|
|
"run.py no longer shares ledger.py's NOK->øre conversion — two copies of a money "
|
|
"conversion is exactly the drift Kø-(p) closed"
|
|
)
|
|
assert not hasattr(run_mod, "_to_ore"), "the private duplicate in run.py must be gone"
|
|
|
|
|
|
async def test_goal_baseline_is_quantized_per_cost_line_not_after_summing(
|
|
tmp_path, monkeypatch, make_recording_client_factory
|
|
) -> None:
|
|
"""LOAD-BEARING: the portfolio goal baseline is quantize-first, so BOTH sides of
|
|
``_goal_limit_if_reached`` are computed in the same order.
|
|
|
|
Observed = 18000002 øre. Quantize-first baseline 18000003 -> a 100 % goal is NOT reached ->
|
|
the project runs. Revert the baseline to ``to_ore(sum(total_cost))`` -> baseline 18000001 ->
|
|
18000002 >= 18000001 -> hard stop before any project runs -> RED."""
|
|
project = _project(tmp_path, unit_cost=_HALF_ORE_UNIT_COST)
|
|
monkeypatch.setattr("portfolio_optimiser.run.load_reference_projects", lambda: (project,))
|
|
factory, _recorded = make_recording_client_factory(_ALIGNED_REPLY)
|
|
|
|
result = await run_portfolio(
|
|
profile="local",
|
|
client_factory=factory,
|
|
ledger=_ledger_observing(_OBSERVED_NOK),
|
|
goals=GoalConfig(portfolio=GoalContract(percent=100.0, mode="hard")),
|
|
)
|
|
|
|
assert result.stopped_early is False, (
|
|
"the pass stopped early: the goal threshold was derived from a sum-first baseline "
|
|
f"({_SUM_FIRST_ORE} øre) instead of the quantize-first one ({_QUANTIZE_FIRST_ORE} øre)"
|
|
)
|
|
assert len(result.runs) == 1, "the single project did not run"
|
|
|
|
|
|
async def test_per_project_goal_baseline_is_also_quantized_per_cost_line(
|
|
tmp_path, monkeypatch, make_recording_client_factory
|
|
) -> None:
|
|
"""LOAD-BEARING (second call site): the PER-PROJECT goal baseline is quantize-first too.
|
|
|
|
The portfolio and per-project baselines are separate call sites, and a fix applied to only one
|
|
leaves the other on the old order — measured: mutating this call site alone survived the whole
|
|
suite before this test existed. A hard per-project goal ``continue``s past its own pid rather
|
|
than stopping the pass, so the tell is that the project never runs. Revert this call site to
|
|
``to_ore(project.total_cost)`` -> baseline 18000001 -> the goal reads as reached -> RED."""
|
|
project = _project(tmp_path, unit_cost=_HALF_ORE_UNIT_COST)
|
|
monkeypatch.setattr("portfolio_optimiser.run.load_reference_projects", lambda: (project,))
|
|
factory, _recorded = make_recording_client_factory(_ALIGNED_REPLY)
|
|
|
|
result = await run_portfolio(
|
|
profile="local",
|
|
client_factory=factory,
|
|
ledger=_ledger_observing(_OBSERVED_NOK),
|
|
goals=GoalConfig(per_project={"ORE-P": GoalContract(percent=100.0, mode="hard")}),
|
|
)
|
|
|
|
assert result.stop_reason is None, (
|
|
"the per-project goal read as reached: its threshold came from a sum-first baseline "
|
|
f"({_SUM_FIRST_ORE} øre) instead of the quantize-first one ({_QUANTIZE_FIRST_ORE} øre)"
|
|
)
|
|
assert len(result.runs) == 1, "the project was skipped by a goal that should not have fired"
|
|
|
|
|
|
def test_realize_converts_through_the_exact_decimal_conversion() -> None:
|
|
"""``realize`` must go through ``to_ore``, not a raw ``float * 100``. ``1.13`` NOK is ``113``
|
|
øre, but ``int(1.13 * 100)`` truncates the binary-float representation to ``112`` — an øre lost
|
|
per realized candidate. Measured: bypassing ``to_ore`` in ``realize`` survived the whole suite
|
|
before this test existed. Replace the call with ``int(claimed * 100)`` -> RED."""
|
|
ledger = _ledger_observing(1.13)
|
|
|
|
assert ledger.entries[0].amount_ore == 113, (
|
|
"realize lost an øre to binary-float drift — its NOK->øre conversion is not going through "
|
|
"to_ore's Decimal path"
|
|
)
|
|
assert ledger.portfolio_total() == 113
|
|
|
|
|
|
async def test_control_whole_ore_lines_do_not_flip_the_outcome(
|
|
tmp_path, monkeypatch, make_recording_client_factory
|
|
) -> None:
|
|
"""CONTROL (causality): with whole-øre cost lines the two orders AGREE, so the same observation
|
|
and goal must reach the goal and stop. Proves the flip above comes from the quantization order —
|
|
not from the goal machinery merely being present."""
|
|
project = _project(tmp_path, unit_cost=_WHOLE_ORE_UNIT_COST)
|
|
monkeypatch.setattr("portfolio_optimiser.run.load_reference_projects", lambda: (project,))
|
|
factory, _recorded = make_recording_client_factory(_ALIGNED_REPLY)
|
|
|
|
result = await run_portfolio(
|
|
profile="local",
|
|
client_factory=factory,
|
|
ledger=_ledger_observing(_OBSERVED_NOK),
|
|
goals=GoalConfig(portfolio=GoalContract(percent=100.0, mode="hard")),
|
|
)
|
|
|
|
assert result.stopped_early is True, (
|
|
"whole-øre lines give baseline 18000000 øre under BOTH orders, and observed 18000002 "
|
|
"exceeds it — the goal must be reached regardless of quantization order"
|
|
)
|