feat(s36): MAF-free costsim skeleton + model-map reader + registration

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0145ZKPLMVeqM47z2jxxokym
This commit is contained in:
Kjell Tore Guttormsen 2026-07-15 09:54:25 +02:00
commit 47147e5f7c
3 changed files with 92 additions and 1 deletions

View file

@ -0,0 +1,46 @@
"""S3.6 — kostnadssimulering før kjøring (D-I pkt. 3): offline, MAF-free cost estimator.
Estimates token/cost for a portfolio run BEFORE running a what-if over the model-map
(models × effort levels) so run cost becomes an informed decision, not a surprise afterwards
(revisjonspakke §0.5 F-INT-5: kjøringskost er en CFO-beslutning).
MAF-free by construction (D7-portable; målbilde context/output-layer rule): this module imports
ONLY stdlib + pydantic never ``agent_framework``/``mcp`` nor the MAF-bound framework modules
(``backends``/``budget``/``contracts``/``run``). It reads ``data/model_map.json`` as PLAIN DATA via a
``__file__``-relative path, deliberately NOT ``importlib.resources.files("portfolio_optimiser")``
(which would import the MAF-bound package ``__init__`` and defeat MAF-freedom). Registered in
``tests/test_okf.py``'s ``_MAF_FREE_MODULES`` and enforced by ``test_okf_is_maf_free`` (direct-import
AST scan) + ``test_costsim_import_is_maf_free`` (transitive import-graph subprocess seam).
All money math is INTEGER øre float NOK is non-deterministic under summation (see ``ledger.py``);
kr is formatted only at the display edge.
"""
from __future__ import annotations
import json
from pathlib import Path
_DATA_DIR = Path(__file__).resolve().parent / "data"
_MODEL_MAP_FILE = _DATA_DIR / "model_map.json"
def _load_model_map(
profile: str, model_map: dict[str, dict[str, str]] | None = None
) -> dict[str, str]:
"""Return the ``role -> model`` sub-map for ``profile`` from ``data/model_map.json``.
Reads the packaged JSON as plain data via a ``__file__``-relative path deliberately NOT
``importlib.resources.files("portfolio_optimiser")``, which would import the MAF-bound package
``__init__`` and defeat the MAF-free guarantee. Tests inject ``model_map`` to avoid touching
shipped config. Fail-fast (``ValueError``) when the profile is absent or malformed.
"""
table = (
model_map
if model_map is not None
else json.loads(_MODEL_MAP_FILE.read_text(encoding="utf-8"))
)
entry = table.get(profile)
if not isinstance(entry, dict):
raise ValueError(f"unknown profile {profile!r} in model map")
return dict(entry)