feat(s54): value_report text + deterministic JSON formatters

This commit is contained in:
Kjell Tore Guttormsen 2026-07-24 01:29:31 +02:00
commit dd566025a4
2 changed files with 128 additions and 0 deletions

View file

@ -8,10 +8,14 @@ risk on cross-dimension overlaps).
from __future__ import annotations
import json
from portfolio_optimiser.ledger import LedgerEntry, SavingsLedger, stamp
from portfolio_optimiser.value_report import (
ProvenanceLine,
build_value_report,
dump_report_json,
format_report_text,
)
_TS = "2026-07-06T00:00:00Z"
@ -82,3 +86,89 @@ def test_provenance_line_per_entry_verbatim_and_deterministic() -> None:
# 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
# --- Step 2: text + JSON formatters (exact NOK bytes SC2, byte-determinism SC6) -------------------
def test_format_report_text_exact_nok_bytes() -> None:
"""SC2: a portfolio total of ``1234567`` øre renders the EXACT Norwegian NOK bytes
``"12\xa0345,67\xa0kr"`` (non-breaking-space thousands + before ``kr``, mirroring
``test_costsim.py:95``), and the empty ledger's ``0`` total renders ``"0,00\xa0kr"``."""
led = SavingsLedger(entries=[_entry("P1", "c-a", 1234567)])
text = format_report_text(build_value_report(led))
assert "12\xa0345,67\xa0kr" in text
empty = format_report_text(build_value_report(SavingsLedger()))
assert "0,00\xa0kr" in empty # valid rc-0 zero-savings output, not an error
def _sc6_entries() -> list[LedgerEntry]:
"""A determinism fixture that INCLUDES a full-``_storage_key``-duplicate pair (``c-dup`` under
``(P1, energi)`` twice, differing only in ``verdict_id``/``provenance``) constructible ONLY by
direct ``SavingsLedger(entries=[...])`` (``add_realized`` would dedup the second). This exercises
the TOTAL provenance sort from Step 1: a triple-only sort key would TIE the pair and let
insertion order flip the bytes. Also carries a genuine cross-dimension overlap (``c-ov`` under
both ``energi`` and ``asfalt``) so ``overlaps`` is non-empty."""
return [
LedgerEntry(
project_id="P1",
dimension="energi",
candidate_identity="c-dup",
amount_ore=1000,
verdict_id="v-a",
provenance="prov-a",
),
LedgerEntry(
project_id="P1",
dimension="energi",
candidate_identity="c-dup",
amount_ore=1000,
verdict_id="v-b",
provenance="prov-b", # full-key dup of the above
),
LedgerEntry(
project_id="P1",
dimension="energi",
candidate_identity="c-ov",
amount_ore=3000,
verdict_id="v-c",
provenance="prov-c",
),
LedgerEntry(
project_id="P1",
dimension="asfalt",
candidate_identity="c-ov",
amount_ore=3000,
verdict_id="v-d",
provenance="prov-d", # cross-dimension overlap
),
LedgerEntry(
project_id="P2",
dimension="energi",
candidate_identity="c-c",
amount_ore=4000,
verdict_id="v-e",
provenance="prov-e",
),
]
def test_output_is_byte_deterministic_regardless_of_order() -> None:
"""SC6: the same entries inserted in a different order -> byte-identical ``format_report_text``
AND ``dump_report_json`` output. The full-key duplicate pair exercises the total provenance
sort; ``overlaps`` tuples serialize to JSON lists (asserted on the parse-back)."""
entries = _sc6_entries()
a = build_value_report(SavingsLedger(entries=list(entries)))
b = build_value_report(SavingsLedger(entries=list(reversed(entries))))
assert format_report_text(a).encode() == format_report_text(b).encode()
assert dump_report_json(a).encode() == dump_report_json(b).encode()
payload = json.loads(dump_report_json(a))
assert payload["portfolio_total_ore"] == 8000 # c-dup 1000 + c-ov 3000 (once) + P2 4000
assert payload["per_project"] == {"P1": 4000, "P2": 4000}
assert isinstance(payload["overlaps"], list) and payload["overlaps"] # non-empty
for ov in payload["overlaps"]:
assert isinstance(ov, list) # tuple serialized as a JSON array
assert ["P1", "c-ov"] in payload["overlaps"]