"""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 road 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" )