"""P21 DEL A - the PROJECT carries the price, so a run against a road normal 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 vegnormal ships one — N100, N200, N500 and R761 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 R761 process numbers (``12.11`` three times on Søråsen, ``1.1.1`` on Lindås) 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 road 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, ASFALT=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]