portfolio-optimiser/tests/test_run_cli_loadbearing.py
Kjell Tore Guttormsen 905b2f9a43 feat(s53): --portfolio mode + --goals/--ledger wiring, GoalReached observable in CLI output
main() gains a --portfolio mode flag dispatching to the EXISTING run_portfolio (not modified):
loads --goals/--ledger/--dimension-config via the fail-fast loaders, passes project_ids=(pid,)
or None (all reference projects), and prints a deterministic goal-stop line
'goal reached: scope=... project=... observed_ore=... limit_ore=... stopped_early=...' from the
returned GoalReached/PortfolioResult. Positional project_id relaxed to nargs='?' and --docs-dir to
optional, with a compensating single-project-mode guard (no pid/no --docs-dir -> rc 1 refusal) so
the legacy contract still fails loudly. Dispatch + guard sit BEFORE the live_dry_run branch
(Pass-2 #3 — appended after, they'd be dead code). Loader failures use the same structured refusal
('portfolio run refused: ...'). Load-bearing pair (RED-first): portfolio-hard goal already met
breaks offline before any client (scope=portfolio, stopped_early=True); per-project-hard control
skips the only pid (scope=project, stopped_early=False) — the two arms force the printed fields to
derive from run_portfolio's real values. Marker 13731 øre. 9 passed, ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KNNiJRk1sSwxgVLS5AobT1
2026-07-23 21:40:35 +02:00

87 lines
3.8 KiB
Python

"""Load-bearing CLI test for the observable GoalReached stop (S5.3 Step 3, SC1).
DETACH THAT TURNS THIS RED: route ``--goals``/``--ledger`` away from ``run_portfolio`` (drop the
wiring in ``main()``'s portfolio dispatch) → no goal-stop line reaches stdout → the positive arm's
marker assertion fails. The positive+control pair proves the printed scope/øre fields flow from
``run_portfolio``'s actual ``GoalReached``/``PortfolioResult`` values — a canned print could not
produce BOTH the portfolio-scope and the project-scope variant from the same wiring.
Both arms are provably OFFLINE: the goal is already met before any project builds a chat client
(portfolio-hard ``break``s at ``run.py:560-567``; per-project-hard ``continue``s past the only pid at
``run.py:571-579`` — no client is ever constructed). Marker value 13731 øre appears nowhere else in
the codebase (repo marker convention). No socket/network is exercised (brief NFR).
"""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from portfolio_optimiser import run
from portfolio_optimiser.ledger import LedgerEntry, SavingsLedger
_MARKER_ORE = 13731 # marker: appears nowhere else in the codebase
@pytest.fixture(autouse=True)
def _isolate_model_env(monkeypatch: pytest.MonkeyPatch) -> None:
"""Hermetic env (verbatim from ``test_live_dry_run.py``) — irrelevant here (no client is built),
kept for parity so an accidental client construction can never read the operator's Foundry env."""
monkeypatch.delenv("PORTFOLIO_MODEL_MAP", raising=False)
monkeypatch.delenv("PORTFOLIO_FOUNDRY_PROJECT_ENDPOINT", raising=False)
def _marker_ledger(tmp_path: Path) -> Path:
"""A ledger with exactly one realized entry totalling the marker øre against FV42-GSV-E1 —
reused by both arms (portfolio_total == per_project_total('FV42-GSV-E1') == 13731)."""
led = SavingsLedger()
led.add_realized(
LedgerEntry(
project_id="FV42-GSV-E1",
dimension="energi",
candidate_identity="c-marker",
amount_ore=_MARKER_ORE,
verdict_id="v-marker",
provenance="prior-hitl",
)
)
p = tmp_path / "ledger.json"
led.save(str(p))
return p
def _write_goals(tmp_path: Path, data: dict) -> Path:
p = tmp_path / "goals.json"
p.write_text(json.dumps(data), encoding="utf-8")
return p
def test_portfolio_hard_goal_reached_is_observable_in_cli(tmp_path, capsys) -> None:
"""POSITIVE: a portfolio-hard goal already met by the ledger stops the whole pass offline and
prints an observable goal-stop line carrying scope=portfolio + the marker øre + stopped_early."""
goals = _write_goals(tmp_path, {"portfolio": {"absolute_ore": _MARKER_ORE, "mode": "hard"}})
ledger = _marker_ledger(tmp_path)
rc = run.main(["--portfolio", "--goals", str(goals), "--ledger", str(ledger)])
assert rc == 0
out = capsys.readouterr().out
assert "goal reached: scope=portfolio" in out
assert f"observed_ore={_MARKER_ORE}" in out
assert "stopped_early=True" in out
def test_per_project_hard_goal_control_distinguishes_scope(tmp_path, capsys) -> None:
"""CONTROL (causality): a per-project-hard goal met on the ONLY selected pid skips it (continue,
no client built) → scope=project, stopped_early=False, and NO portfolio-scope line. Proves the
printed fields are the actual run_portfolio values, not a canned string."""
goals = _write_goals(
tmp_path, {"per_project": {"FV42-GSV-E1": {"absolute_ore": _MARKER_ORE, "mode": "hard"}}}
)
ledger = _marker_ledger(tmp_path)
rc = run.main(["FV42-GSV-E1", "--portfolio", "--goals", str(goals), "--ledger", str(ledger)])
assert rc == 0
out = capsys.readouterr().out
assert "scope=project" in out
assert "stopped_early=False" in out
assert "scope=portfolio" not in out