The context sets, the packaged knowledge bases and the example bundles are replaced by one fictitious example set about IT operations in an invented organisation: three context sets (serverrom-2027, driftsavtale-2027 and the two-base drift-og-avtale-2027), two synthetic knowledge bases under src/portfolio_optimiser/data/kunnskapsbaser and two example bundles under src/portfolio_optimiser/data/bundles. Numbers, codes and structural values in tests and fixtures are kept; names, ids and wording change. Dated measurement documents that only recorded runs on the replaced material are deleted. Gate figures measured on the new set are not comparable with earlier ones. The exclusion gate from the previous commit is green: 0 tracked files hit outside the shared/ subtree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
397 lines
16 KiB
Python
397 lines
16 KiB
Python
"""P21 DEL A - the PROJECT carries the price, so a run on a requirements base can be anchored.
|
|
|
|
**The measurement this closes.** Four paid stress rounds (P16/P18/P19/P17b/P20) ran ENTIRELY
|
|
un-anchored. The cause is one line: the only file loader reads ``cost-baseline.json`` out of the
|
|
BUNDLE directory (``okf.load_optional_cost_baseline``), and no requirements base or process
|
|
catalogue ships one — they are knowledge, and knowledge carries requirements, never amounts. The
|
|
validator's stage 0 — the one stage that can tell an invented cost line from a line this project
|
|
actually buys — was therefore skipped in every single one, and ``validated`` could not mean what it
|
|
says: P20 G1/G2 measured real process-catalogue numbers (``12.11`` three times on one set,
|
|
``1.1.1`` on another) validating with amounts nobody had anywhere.
|
|
|
|
``--cost-baseline FILE`` is PM decision (e), taken over three alternatives P20 wrote down: (a)
|
|
refusing every requirement-shaped code un-anchored would make the one realistic context set
|
|
unmeasurable, (b) ``--require-cost-baseline`` as a default would leave no stress test at all, and
|
|
(c) K2's priced schedule is refused by MAJOR-4's own stated limit. (e) puts the price where it
|
|
belongs — with the project — and stage 0 judges again.
|
|
|
|
Arms: (a) the library seam anchors * (b) control: without it the same base is un-anchored *
|
|
(c) it is the SAME baseline the validator is handed, so stage 0 really judges * (d) two sources for
|
|
one baseline are refused at the library seam * (e) the free trip anchors too * (f) a missing file
|
|
and (g) a malformed one are refused with ``load_cost_baseline``'s own error classes * (h)(i)(j)(k)
|
|
four CLI refusals BY NAME, each with an rc-0 control on an argv that would otherwise be accepted *
|
|
(l) the CLI wiring, measured on the stamp * (m) the notice * (n) EVERY base of an ``--across-bundle``
|
|
pass gets the SAME schedule.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from portfolio_optimiser import okf, run
|
|
from portfolio_optimiser.ir import CostBaseline, CostBaselineLine
|
|
from portfolio_optimiser.simulation import ScriptedChatClient
|
|
from portfolio_optimiser.validator import Rejection, validate_proposal
|
|
|
|
_EXAMPLES = Path(__file__).resolve().parents[1] / "shared" / "examples"
|
|
#: A base that ships NO ``cost-baseline.json`` — the whole class this flag exists for.
|
|
_UNPRICED_SOURCE = _EXAMPLES / "bygg-energi-mikro"
|
|
|
|
_IR_PROJECTION = {
|
|
"project_id": "P-KNOWLEDGE",
|
|
"measure": "PLACEHOLDER - authored by this test to satisfy the bundle contract",
|
|
"affected_items": [{"code": "KNOW-1", "quantity": 10, "unit_cost": 100.0}],
|
|
"claimed_saving_nok": 500.0,
|
|
}
|
|
|
|
|
|
def _runnable(tmp_path: Path, *, name: str = "base") -> str:
|
|
root = tmp_path / name
|
|
shutil.copytree(_UNPRICED_SOURCE, root)
|
|
(root / "validator-input.json").write_text(json.dumps(_IR_PROJECTION), encoding="utf-8")
|
|
assert not (root / "cost-baseline.json").exists(), "the fixture must be UNPRICED"
|
|
return str(root)
|
|
|
|
|
|
def _schedule(tmp_path: Path, *, name: str = "cost-baseline.json", **codes: float) -> str:
|
|
"""The PROJECT's own price schedule, written OUTSIDE every knowledge base — which is the whole
|
|
point: ``safe_resolve`` guards the bundle door, and a project's schedule is legitimately not in
|
|
a bundle."""
|
|
path = tmp_path / name
|
|
path.write_text(
|
|
json.dumps(
|
|
{
|
|
"project_id": "P-KNOWLEDGE",
|
|
"items": {
|
|
code: {"quantity": 10.0, "unit_cost": unit} for code, unit in codes.items()
|
|
},
|
|
}
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
return str(path)
|
|
|
|
|
|
def _scripted(sink: list[str] | None = None) -> Any:
|
|
return lambda role: ScriptedChatClient(sink=sink, role=role, default_reply="ok")
|
|
|
|
|
|
# --- (a)/(b)/(c)/(d)/(e) the library seam ---------------------------------------------------------
|
|
|
|
|
|
async def test_a_project_schedule_anchors_a_base_that_ships_none(tmp_path: Path) -> None:
|
|
bundle_dir = _runnable(tmp_path)
|
|
baseline = okf.load_cost_baseline_file(_schedule(tmp_path, RIGG=1000.0))
|
|
|
|
report = await run.run_project(
|
|
"P-KNOWLEDGE",
|
|
"local",
|
|
docs_dir=bundle_dir,
|
|
bundle_dir=bundle_dir,
|
|
cost_baseline=baseline,
|
|
live_dry_run=True,
|
|
)
|
|
|
|
assert isinstance(report, run.DryRunReport)
|
|
assert report.cost_baseline_anchored is True
|
|
|
|
|
|
async def test_control_without_the_schedule_the_same_base_is_unanchored(tmp_path: Path) -> None:
|
|
"""The discriminator for the arm above: the SAME base, the flag removed. Without this, "it is
|
|
anchored" could be a property of the fixture rather than of the file."""
|
|
bundle_dir = _runnable(tmp_path)
|
|
|
|
report = await run.run_project(
|
|
"P-KNOWLEDGE", "local", docs_dir=bundle_dir, bundle_dir=bundle_dir, live_dry_run=True
|
|
)
|
|
|
|
assert isinstance(report, run.DryRunReport)
|
|
assert report.cost_baseline_anchored is False
|
|
|
|
|
|
def test_the_supplied_schedule_is_what_stage_0_judges_against() -> None:
|
|
"""The stamp says anchored; this says the anchoring DOES something.
|
|
|
|
An implementation that read the file, stamped ``True`` and handed the validator ``None`` would
|
|
pass every other arm here — the mutation the order names as (i). Stage 0 either refuses a code
|
|
the project does not buy or it does not, and that is the only thing worth having.
|
|
"""
|
|
baseline = CostBaseline(
|
|
project_id="P-KNOWLEDGE",
|
|
items={"RIGG": CostBaselineLine(quantity=10.0, unit_cost=1000.0)},
|
|
)
|
|
from portfolio_optimiser.ir import AffectedItem, SavingsProposal
|
|
|
|
invented = SavingsProposal(
|
|
project_id="P-KNOWLEDGE",
|
|
measure="m",
|
|
affected_items=[AffectedItem(code="INDEKS-01", quantity=10.0, unit_cost=1000.0)],
|
|
claimed_saving_nok=100.0,
|
|
)
|
|
outcome = validate_proposal(invented, baseline=baseline)
|
|
assert isinstance(outcome, Rejection)
|
|
assert "cost baseline" in outcome.reason
|
|
# The control: the SAME magnitudes on a code the project DOES buy are not refused by stage 0.
|
|
real = SavingsProposal(
|
|
project_id="P-KNOWLEDGE",
|
|
measure="m",
|
|
affected_items=[AffectedItem(code="RIGG", quantity=10.0, unit_cost=1000.0)],
|
|
claimed_saving_nok=100.0,
|
|
)
|
|
second = validate_proposal(real, baseline=baseline)
|
|
assert not (isinstance(second, Rejection) and "cost baseline" in second.reason)
|
|
|
|
|
|
async def test_two_sources_for_one_baseline_are_refused_at_the_library_seam(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""(d) Checked in ``run_project`` and not only in the CLI: the library takes the same two
|
|
arguments, and a library caller must not reach a state the CLI refuses by name."""
|
|
bundle_dir = _runnable(tmp_path)
|
|
baseline = okf.load_cost_baseline_file(_schedule(tmp_path, RIGG=1000.0))
|
|
|
|
with pytest.raises(ValueError, match="two sources for one baseline"):
|
|
await run.run_project(
|
|
"P-KNOWLEDGE",
|
|
"local",
|
|
docs_dir=bundle_dir,
|
|
bundle_dir=bundle_dir,
|
|
cost_baseline=baseline,
|
|
derive_cost_baseline=True,
|
|
live_dry_run=True,
|
|
)
|
|
|
|
|
|
async def test_it_satisfies_the_anchoring_requirement(tmp_path: Path) -> None:
|
|
"""(e) ``--require-cost-baseline`` is the guarantee; this is one of the two ways to meet it."""
|
|
bundle_dir = _runnable(tmp_path)
|
|
baseline = okf.load_cost_baseline_file(_schedule(tmp_path, RIGG=1000.0))
|
|
|
|
report = await run.run_project(
|
|
"P-KNOWLEDGE",
|
|
"local",
|
|
docs_dir=bundle_dir,
|
|
bundle_dir=bundle_dir,
|
|
cost_baseline=baseline,
|
|
require_cost_baseline=True,
|
|
live_dry_run=True,
|
|
)
|
|
|
|
assert isinstance(report, run.DryRunReport)
|
|
assert report.cost_baseline_anchored is True
|
|
|
|
|
|
# --- (f)/(g) the loader's error classes -----------------------------------------------------------
|
|
|
|
|
|
def test_a_missing_schedule_is_refused(tmp_path: Path) -> None:
|
|
with pytest.raises(FileNotFoundError):
|
|
okf.load_cost_baseline_file(str(tmp_path / "nope.json"))
|
|
|
|
|
|
def test_a_malformed_schedule_is_refused(tmp_path: Path) -> None:
|
|
"""``load_cost_baseline``'s own classes, and there is no tolerant twin: this path exists only
|
|
because an operator NAMED a file, so degrading its absence would answer an explicit order with
|
|
a silently un-anchored run."""
|
|
bad = tmp_path / "bad.json"
|
|
bad.write_text('{"project_id": "P", "items": {"X": {"quantity": -1, "unit_cost": 0}}}', "utf-8")
|
|
with pytest.raises(ValidationError):
|
|
okf.load_cost_baseline_file(str(bad))
|
|
|
|
|
|
def test_the_bundle_loader_still_parses_through_the_same_seam(tmp_path: Path) -> None:
|
|
"""ONE parse, two doors (kø-(p)). What differs is the RESOLUTION: ``safe_resolve`` stays on the
|
|
bundle door alone, because a project's own schedule is legitimately outside every base."""
|
|
base = tmp_path / "b"
|
|
base.mkdir()
|
|
(base / "cost-baseline.json").write_text(
|
|
json.dumps({"project_id": "P", "items": {"X": {"quantity": 1, "unit_cost": 2}}}), "utf-8"
|
|
)
|
|
assert okf.load_cost_baseline(str(base)).items["X"].unit_cost == 2.0
|
|
|
|
|
|
# --- (h)/(i)/(j)/(k) four CLI refusals, each with an rc-0 control ----------------------------------
|
|
|
|
|
|
def test_cli_requires_a_knowledge_base(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
|
"""(h) On the reference-project path the baseline IS ``Project.cost_items``, so a file there is
|
|
a second source for one fact with nothing to break the tie (``--require-cost-baseline``'s
|
|
reason)."""
|
|
rc = run.main(["P1", "--docs-dir", "docs", "--cost-baseline", _schedule(tmp_path, RIGG=1000.0)])
|
|
|
|
assert rc == 1
|
|
assert "--cost-baseline" in capsys.readouterr().err
|
|
|
|
|
|
def test_cli_refuses_two_sources_by_name(
|
|
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""(i) BY NAME so the operator hears WHICH two flags conflict, rather than a traceback."""
|
|
bundle_dir = _runnable(tmp_path)
|
|
schedule = _schedule(tmp_path, RIGG=1000.0)
|
|
|
|
assert (
|
|
run.main(
|
|
[
|
|
"P-KNOWLEDGE",
|
|
"--bundle-dir",
|
|
bundle_dir,
|
|
"--cost-baseline",
|
|
schedule,
|
|
"--live-dry-run",
|
|
]
|
|
)
|
|
== 0
|
|
), "the control argv must be ACCEPTED"
|
|
capsys.readouterr()
|
|
|
|
rc = run.main(
|
|
[
|
|
"P-KNOWLEDGE",
|
|
"--bundle-dir",
|
|
bundle_dir,
|
|
"--cost-baseline",
|
|
schedule,
|
|
"--derive-cost-baseline",
|
|
"--live-dry-run",
|
|
]
|
|
)
|
|
|
|
assert rc == 1
|
|
err = capsys.readouterr().err
|
|
assert "--cost-baseline" in err and "--derive-cost-baseline" in err
|
|
|
|
|
|
def test_cli_is_refused_in_portfolio_mode(
|
|
tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""(j) A portfolio pass keys on PROJECTS, each already anchored by its own ``cost_items``, so
|
|
ONE file could be right for at most one row out of N. BY NAME, its neighbours' reason."""
|
|
rc = run.main(["--portfolio", "--cost-baseline", _schedule(tmp_path, RIGG=1000.0)])
|
|
|
|
assert rc == 1
|
|
assert "--portfolio" in capsys.readouterr().err
|
|
|
|
|
|
def test_cli_is_refused_in_report_mode(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
|
"""(k) Report mode returns ABOVE every dispatch, so an omission from the allowlist is a SILENT
|
|
DROP — the file would be accepted, nothing anchored, exit 0 (the F4 gap)."""
|
|
ledger = tmp_path / "ledger.json"
|
|
ledger.write_text("[]", encoding="utf-8")
|
|
|
|
assert run.main(["--report", "--ledger", str(ledger)]) == 0, "the control argv must be ACCEPTED"
|
|
capsys.readouterr()
|
|
|
|
rc = run.main(
|
|
["--report", "--ledger", str(ledger), "--cost-baseline", _schedule(tmp_path, RIGG=1000.0)]
|
|
)
|
|
|
|
assert rc == 1
|
|
assert "mode-exclusive" in capsys.readouterr().err
|
|
|
|
|
|
# --- (l)/(m) the CLI wiring and the notice --------------------------------------------------------
|
|
|
|
|
|
def test_cli_wiring_anchors_the_dry_run_and_says_so(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""(l)+(m) The flag must REACH ``run_project``, and the operator must be able to see that it
|
|
did on the FREE trip. rc 0 alone proves neither, so the discriminators are the two lines: the
|
|
un-anchored warning is GONE and the positive line names the file and the count."""
|
|
monkeypatch.delenv("PORTFOLIO_MODEL_MAP", raising=False)
|
|
bundle_dir = _runnable(tmp_path)
|
|
argv = ["P-KNOWLEDGE", "--bundle-dir", bundle_dir, "--live-dry-run"]
|
|
|
|
assert run.main(argv) == 0
|
|
before = capsys.readouterr().out
|
|
assert "Cost baseline: NONE in the bundle" in before
|
|
assert "Cost baseline: 2 lines from" not in before
|
|
|
|
schedule = _schedule(tmp_path, RIGG=1000.0, LISENS=250.0)
|
|
assert run.main([*argv, "--cost-baseline", schedule]) == 0
|
|
after = capsys.readouterr().out
|
|
|
|
assert "Cost baseline: NONE in the bundle" not in after
|
|
assert f"Cost baseline: 2 lines from {schedule}" in after
|
|
|
|
|
|
def test_the_notice_is_omitted_when_nobody_named_a_file() -> None:
|
|
"""Omission where it is unambiguous — there is exactly one way to supply a schedule, so
|
|
silence means nobody did (``cost_baseline_notice``'s rule, kept)."""
|
|
assert run.cost_baseline_source_notice(None, 0) is None
|
|
assert run.cost_baseline_source_notice("x.json", 1) == (
|
|
" Cost baseline: 1 line from x.json — the validator's stage 0 reconciles every proposed "
|
|
"cost line against this project's own schedule"
|
|
)
|
|
|
|
|
|
# --- (n) every base of a multi-base pass ----------------------------------------------------------
|
|
|
|
|
|
async def test_every_base_of_an_across_bundle_pass_gets_the_same_schedule(
|
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""(n) ONE project has ONE price schedule, so it anchors EVERY base.
|
|
|
|
The order's mutation (ii) is "only the first base gets it", and the assert is therefore per
|
|
BASE: a recorder that stopped at the first call would pass on exactly that mutation — the
|
|
vacuous-gate class this repo keeps measuring. Both calls are recorded and both must carry the
|
|
SAME object, because two reads of one file is already one resolution too many (kø-(p)).
|
|
"""
|
|
from portfolio_optimiser.mandate import Approach, Mandate
|
|
|
|
first = _runnable(tmp_path, name="one")
|
|
second = _runnable(tmp_path, name="two")
|
|
baseline = okf.load_cost_baseline_file(_schedule(tmp_path, RIGG=1000.0))
|
|
calls: list[dict[str, Any]] = []
|
|
|
|
async def _recorder(project_id: str, profile: Any = "local", **kwargs: Any) -> Any:
|
|
calls.append({"project_id": project_id, **kwargs})
|
|
|
|
class _Stub:
|
|
coverage: tuple[Any, ...] = ()
|
|
provenance = None
|
|
|
|
return _Stub()
|
|
|
|
monkeypatch.setattr(run, "run_project", _recorder)
|
|
|
|
mandate = Mandate(
|
|
objective="o",
|
|
success_criteria="s",
|
|
approaches=[
|
|
Approach(
|
|
id="a1",
|
|
label="one",
|
|
affected_codes=["RIGG"],
|
|
claimed_saving_nok=1.0,
|
|
bundle_id="one",
|
|
),
|
|
Approach(
|
|
id="a2",
|
|
label="two",
|
|
affected_codes=["RIGG"],
|
|
claimed_saving_nok=1.0,
|
|
bundle_id="two",
|
|
),
|
|
],
|
|
)
|
|
await run.run_mandate_across_bundles(mandate, [first, second], "local", cost_baseline=baseline)
|
|
|
|
assert len(calls) == 2, f"the dispatch ran {len(calls)} base(s), not two"
|
|
assert [c["bundle_dir"] for c in calls] == [first, second]
|
|
assert [c.get("cost_baseline") for c in calls] == [baseline, baseline], (
|
|
"a base was dispatched without the project's own schedule"
|
|
)
|
|
# The control: without the flag, no base is handed one — so the arm above measures the flag
|
|
# rather than a default.
|
|
calls.clear()
|
|
await run.run_mandate_across_bundles(mandate, [first, second], "local")
|
|
assert [c.get("cost_baseline") for c in calls] == [None, None]
|