feat(s53): load_dimension fail-fast loader (MAF-free, mirrors load_goal_config)

New load_dimension(str | Path) in dimension.py: is_file() -> FileNotFoundError,
then Dimension.model_validate_json -> pydantic.ValidationError on malformed shape.
Fail-fast because dimension config is authoritative startup input (contrast the
tolerant verdict-inbox RAW layer). Stdlib + pydantic only — stays MAF-free
(test_okf_is_maf_free AST guard green). Exported from __init__.py in both the
import block and __all__. RED-first: 3 loader tests failed on the missing import,
green after. tests/test_dimension.py + test_okf.py: 29 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNNiJRk1sSwxgVLS5AobT1
This commit is contained in:
Kjell Tore Guttormsen 2026-07-23 21:31:32 +02:00
commit 124b7aedde
3 changed files with 59 additions and 2 deletions

View file

@ -14,7 +14,7 @@ from __future__ import annotations
import pytest
from pydantic import ValidationError
from portfolio_optimiser.dimension import Dimension, admits
from portfolio_optimiser.dimension import Dimension, admits, load_dimension
def _energy_dim(*, prefixes: frozenset[str] = frozenset()) -> Dimension:
@ -74,3 +74,35 @@ def test_dimension_empty_label_raises() -> None:
label="",
allowed_measure_types=frozenset({"energy_efficiency"}),
)
# --- Step 1 (S5.3): fail-fast load_dimension loader (mirrors load_goal_config) -------------------
def test_load_dimension_round_trip(tmp_path) -> None:
"""A valid dimension JSON round-trips through ``load_dimension`` (accepts str | Path)."""
dim = Dimension(
id="energi",
label="Energi",
allowed_measure_types=frozenset({"energy_efficiency"}),
allowed_code_prefixes=frozenset({"07"}),
)
p = tmp_path / "dim.json"
p.write_text(dim.model_dump_json(), encoding="utf-8")
assert load_dimension(p) == dim
assert load_dimension(str(p)) == dim # str path also accepted
def test_load_dimension_missing_file_raises(tmp_path) -> None:
"""A missing file fails fast with ``FileNotFoundError`` — authoritative startup config,
NOT a tolerant RAW inbox layer (contrast the verdict inbox)."""
with pytest.raises(FileNotFoundError):
load_dimension(tmp_path / "does-not-exist.json")
def test_load_dimension_malformed_shape_raises(tmp_path) -> None:
"""Malformed content (missing required fields) fails fast with ``ValidationError``."""
bad = tmp_path / "dim.json"
bad.write_text('{"id": "energi"}', encoding="utf-8") # missing label + allowed_measure_types
with pytest.raises(ValidationError):
load_dimension(bad)