"""S5.4 — verdirapport unit tests: roll-up correctness (SC1), exact NOK (SC2), determinism (SC6). In-process build over a hand-constructed ``SavingsLedger``; local ``_entry(...)`` factory (copied from ``tests/test_ledger.py:22-37``). The roll-up must call the ledger accessors (``per_project_total`` / ``portfolio_total`` / ``overlaps``), never re-sum ``.entries`` (double-count risk on cross-dimension overlaps). """ from __future__ import annotations from portfolio_optimiser.ledger import LedgerEntry, SavingsLedger, stamp from portfolio_optimiser.value_report import ( ProvenanceLine, build_value_report, ) _TS = "2026-07-06T00:00:00Z" def _entry( project_id: str, candidate_identity: str, amount_ore: int, *, dimension: str = "energi", verdict_id: str = "v1", ) -> LedgerEntry: return LedgerEntry( project_id=project_id, dimension=dimension, candidate_identity=candidate_identity, amount_ore=amount_ore, verdict_id=verdict_id, provenance=stamp(approver="ekspert", experiment="fase1-sim", timestamp=_TS), ) def _mixed_ledger() -> SavingsLedger: """>=2 projects, >=2 dimensions, one cross-dimension overlap (the same candidate ``c-a`` realized under both ``energi`` and ``asfalt`` in P1 -> counted ONCE by the deduped total, FLAGGED).""" led = SavingsLedger() led.add_realized(_entry("P1", "c-a", 1000, dimension="energi")) led.add_realized(_entry("P1", "c-a", 1000, dimension="asfalt")) # overlap: same candidate id led.add_realized(_entry("P1", "c-b", 2500, dimension="energi")) led.add_realized(_entry("P2", "c-c", 4000, dimension="energi")) return led def test_build_value_report_matches_ledger_accessors() -> None: """SC1: per-project + portfolio totals equal the ledger accessors; ``overlaps`` equals ``ledger.overlaps()`` (the cross-dimension overlap counted ONCE, flagged).""" led = _mixed_ledger() report = build_value_report(led) for pid in {e.project_id for e in led.entries}: assert report.per_project[pid] == led.per_project_total(pid) assert report.portfolio_total_ore == led.portfolio_total() assert report.overlaps == led.overlaps() # overlap counted once: P1 = c-a (1000, once across 2 dims) + c-b (2500) = 3500 assert report.per_project["P1"] == 3500 assert report.per_project["P2"] == 4000 assert report.overlaps == [("P1", "c-a")] def test_provenance_line_per_entry_verbatim_and_deterministic() -> None: """SC1 (Revision #9): ``.provenance`` carries one ``ProvenanceLine`` per ledger entry with verbatim ``verdict_id`` + ``provenance`` string, deterministically ordered.""" led = _mixed_ledger() report = build_value_report(led) assert len(report.provenance) == len(led.entries) for line in report.provenance: assert isinstance(line, ProvenanceLine) by_key = {(m.project_id, m.dimension, m.candidate_identity): m for m in report.provenance} for e in led.entries: line = by_key[(e.project_id, e.dimension, e.candidate_identity)] assert line.verdict_id == e.verdict_id # verbatim, never re-minted assert line.provenance == e.provenance # entry's own string, no verdict-store join assert line.amount_ore == e.amount_ore # deterministically ordered: same entries in a different insertion order -> identical provenance shuffled = SavingsLedger(entries=list(reversed(led.entries))) assert build_value_report(shuffled).provenance == report.provenance