portfolio-optimiser/tests/test_row6_declaration_rule_loadbearing.py
Kjell Tore Guttormsen 37547fe292
refactor(examples): replace sector-specific example material with generic, fictitious examples
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>
2026-09-23 15:04:21 +02:00

292 lines
12 KiB
Python

"""Row 6 — a proposal whose approach declared no requirement cannot carry ``validated``.
Before this rule every numeric stage could pass and the run would stamp ``validated`` whether or
not anything in the knowledge base had been named as binding the direction. Measured on stress
round 6: three falsification arms validated, and every one of the ten validated approaches had only
run-level declarations — which a judge cannot attribute to any one approach.
What each arm pins:
(a) on the shipped micro base, where the debate holds ``declare_requirement``, a silent approach
that used to validate is now ``unsupported`` — and so is the run's own proposal;
(b) a declaration filed under the approach's id is what lets it validate, and ONLY that approach;
(c) a requirement written into the mandate counts as the approach's own declaration;
(d) the declaration's quality is not judged: any requirement the run read is accepted;
(e) an id no approach carries is refused with the valid ids named, and nothing is recorded;
(f) ``unsupported`` is never counted, summed or selected as a success, yet the validator's own
ruling is kept on the record (``provenance.validator_decision``) and in the artefact;
(g) with the rule inactive (no declaration rung offered) the old ruling stands;
(h) the judge reads the addressed declaration as the approach's own, a legacy one as ``run``.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
import pytest
from portfolio_optimiser import okf, outbox, stress
from portfolio_optimiser.explore import DeclaredRequirement, navigator_tools, requirement_payload
from portfolio_optimiser.ir import AffectedItem, SavingsProposal
from portfolio_optimiser.mandate import Approach, BindingRequirement, Mandate, settle
from portfolio_optimiser.run import _evaluate_mandate, run_project
from portfolio_optimiser.simulation import scripted_factory
from portfolio_optimiser.validator import (
UNSUPPORTED_REASON,
Rejection,
Unsupported,
ValidatedProposal,
rejection_stage,
)
from portfolio_optimiser.verdicts import VerdictStore
_BUNDLE = Path(__file__).resolve().parents[1] / "shared" / "examples" / "bygg-energi-mikro"
_BASE_ID = "bygg-energi-mikro"
_PID = "BYGG-KONTOR-NORD"
_REPLY = (
'{"measure":"LED-retrofit av kontorbelysning","affected_items":'
'[{"code":"ENERGI-TOTAL-EL","quantity":300000,"unit_cost":1.0}],"claimed_saving_nok":30000}'
)
_CHECKER = "Reasoning holds.\nVERDICT: APPROVE"
_CONCEPTS = [f.name for f in okf.navigate_bundle(str(_BUNDLE)).context_files]
def _mandate(**approach: Any) -> Mandate:
return Mandate(
objective="Kutt energikostnad",
approaches=(Approach(id="a1", label="LED-retrofit", description="reason", **approach),),
)
def _script(*declare_for: str, ref: str = "Krav 1.1-1") -> dict[str, Any]:
steps: list[Any] = []
if declare_for:
steps += [
{"call": "read_file", "args": {"bundle_id": _BASE_ID, "path": n}} for n in _CONCEPTS[:3]
]
steps += [
{
"call": "declare_requirement",
"args": {
"bundle_id": _BASE_ID,
"path": _CONCEPTS[0],
"ref": ref,
"approach_id": aid,
},
}
for aid in declare_for
]
return {"proposer": [*steps, _REPLY, _REPLY, _REPLY, _REPLY, _REPLY], "checker": _CHECKER}
async def _run(tmp_path: Path, script: dict[str, Any], mandate: Mandate) -> Any:
return await run_project(
_PID,
"local",
docs_dir=str(_BUNDLE),
bundle_dir=str(_BUNDLE),
store=VerdictStore(verdicts=[]),
client_factory=scripted_factory(script, []),
mandate=mandate,
outbox_dir=str(tmp_path),
run_id="r6",
)
def _statuses(result: Any) -> dict[str, str]:
return {row.id: row.status for row in result.coverage}
# ---------------------------------------------------------------------------------------------
# (a)-(d) the rule on the real run
# ---------------------------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_a_silent_approach_on_the_micro_base_is_unsupported(tmp_path: Path) -> None:
result = await _run(tmp_path, _script(), _mandate())
assert _statuses(result) == {"a1": "unsupported", "own-proposal": "unsupported"}
assert {row.detail for row in result.coverage} == {UNSUPPORTED_REASON}
@pytest.mark.asyncio
async def test_only_the_approach_that_declared_is_validated(tmp_path: Path) -> None:
result = await _run(tmp_path, _script("a1"), _mandate())
assert _statuses(result) == {"a1": "validated", "own-proposal": "unsupported"}
debate = json.loads((tmp_path / "r6-debate.json").read_text(encoding="utf-8"))
assert [r["approach_id"] for r in debate["requirements"]] == ["a1"]
@pytest.mark.asyncio
async def test_a_declaration_for_the_own_proposal_does_not_stand_in(tmp_path: Path) -> None:
result = await _run(tmp_path, _script("own-proposal"), _mandate())
assert _statuses(result) == {"a1": "unsupported", "own-proposal": "validated"}
@pytest.mark.asyncio
async def test_a_requirement_written_into_the_mandate_counts(tmp_path: Path) -> None:
requirement = BindingRequirement(path=_CONCEPTS[0], ref="Krav 1.1-1")
result = await _run(tmp_path, _script(), _mandate(requirement=requirement))
assert _statuses(result)["a1"] == "validated"
@pytest.mark.asyncio
async def test_the_declarations_quality_is_not_judged(tmp_path: Path) -> None:
result = await _run(tmp_path, _script("a1", ref="anything at all"), _mandate())
assert _statuses(result)["a1"] == "validated"
# ---------------------------------------------------------------------------------------------
# (e) the address
# ---------------------------------------------------------------------------------------------
def _tool(approach_ids: list[str] | None) -> tuple[Any, list[DeclaredRequirement]]:
opened: list[Any] = []
declared: list[DeclaredRequirement] = []
tools = navigator_tools(
[str(_BUNDLE)], opened=opened, requirements=declared, approach_ids=approach_ids
)
from portfolio_optimiser.explore import ToolCall
opened += [ToolCall(name="read_file", bundle_id=_BASE_ID, path=n) for n in _CONCEPTS[:3]]
return {t.name: t for t in tools}["declare_requirement"], declared
def test_an_unknown_approach_id_is_refused_naming_the_valid_ones() -> None:
tool, declared = _tool(["a1", "own-proposal"])
reply = tool.func(bundle_id=_BASE_ID, path=_CONCEPTS[0], ref="K", approach_id="a9")
assert reply["refusal"] == "UnknownApproach"
assert "'a1'" in reply["refused"] and "'own-proposal'" in reply["refused"]
assert declared == []
ok = tool.func(bundle_id=_BASE_ID, path=_CONCEPTS[0], ref="K", approach_id="a1")
assert ok["declared"] is True and ok["approach_id"] == "a1"
assert declared == [
DeclaredRequirement(bundle_id=_BASE_ID, path=_CONCEPTS[0], ref="K", approach_id="a1")
]
def test_without_a_commission_any_label_is_recorded_but_never_an_empty_one() -> None:
tool, declared = _tool(None)
assert tool.func(bundle_id=_BASE_ID, path=_CONCEPTS[0], ref="K", approach_id=" ")["refusal"]
assert declared == []
tool.func(bundle_id=_BASE_ID, path=_CONCEPTS[0], ref="K", approach_id="LED-retrofit")
assert requirement_payload(declared) == [
{"bundle_id": _BASE_ID, "path": _CONCEPTS[0], "ref": "K", "approach_id": "LED-retrofit"}
]
# ---------------------------------------------------------------------------------------------
# (f) never counted as a success; the validator's ruling kept
# ---------------------------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_unsupported_is_never_a_success_but_keeps_the_validators_ruling(
tmp_path: Path,
) -> None:
result = await _run(tmp_path, _script(), _mandate())
assert isinstance(result.outcome, Unsupported)
assert not isinstance(result.outcome, ValidatedProposal)
assert result.provenance.validator_decision == "validated"
text = settle(result.coverage)
assert "UNSUPPORTED" in text and "Validated: 0 of 2" in text
artefact = json.loads((tmp_path / "r6-a1-outcome.json").read_text(encoding="utf-8"))
assert artefact["outcome_type"] == "unsupported"
assert artefact["reason"] == UNSUPPORTED_REASON
assert artefact["p50"] > 0
proposal = json.loads((tmp_path / "r6-a1-proposal.json").read_text(encoding="utf-8"))
assert proposal["provenance"]["validator_decision"] == "validated"
def test_the_stage_label_names_the_new_falsifier() -> None:
assert rejection_stage(UNSUPPORTED_REASON) == "unsupported"
def _validated() -> ValidatedProposal:
proposal = SavingsProposal(
project_id=_PID,
measure="m",
affected_items=[AffectedItem(code="X", quantity=10.0, unit_cost=10.0)],
claimed_saving_nok=5.0,
)
return ValidatedProposal(proposal=proposal, p10=1.0, p50=2.0, p90=3.0, nominal_feasible=4.0)
def test_the_outcome_payload_carries_both_halves() -> None:
v = _validated()
payload = outbox.outcome_payload(
Unsupported(proposal=v.proposal, reason=UNSUPPORTED_REASON, validated=v),
checker_verdict="approve",
verdict_id="k",
)
assert (payload["outcome_type"], payload["p90"]) == ("unsupported", 3.0)
# ---------------------------------------------------------------------------------------------
# (g) inactive rule
# ---------------------------------------------------------------------------------------------
@pytest.mark.asyncio
@pytest.mark.parametrize(
("declared", "status"), [(None, "validated"), ([], "unsupported")], ids=["inactive", "active"]
)
async def test_the_rule_acts_only_when_a_declaration_rung_was_offered(
declared: list[DeclaredRequirement] | None, status: str
) -> None:
v = _validated()
async def evaluate(_approach: Approach | None) -> ValidatedProposal | Rejection:
return v
_, rows, _ = await _evaluate_mandate(
Mandate(objective="o", approaches=(Approach(id="a1", label="l"),)),
evaluate,
declared=declared,
)
assert {r.id: r.status for r in rows} == {"a1": status, "own-proposal": status}
# ---------------------------------------------------------------------------------------------
# (h) the judge
# ---------------------------------------------------------------------------------------------
def test_the_judge_attributes_addressed_declarations_and_labels_legacy_ones() -> None:
a1 = Approach(id="a1", label="l")
new = [{"path": "p1", "approach_id": "a1"}, {"path": "p2", "approach_id": "own-proposal"}]
assert stress._attributable(a1, new) == (("p1",), "approach")
assert stress._attributable(Approach(id="a2", label="l"), new) == ((), "absent")
assert stress._attributable(a1, [{"path": "p0"}]) == (("p0",), "run")
@pytest.mark.asyncio
async def test_a_run_offered_no_declaration_rung_keeps_the_validators_ruling(
tmp_path: Path,
) -> None:
"""The reference path holds no knowledge base, so no ``declare_requirement`` exists there: nothing
could have been declared, and the rule stays out of it. Drives the REAL ``run_project`` —
the arm above only proves ``_evaluate_mandate`` honours ``declared=None``, not that the run
passes it."""
from portfolio_optimiser.reference_domain import load_reference_projects
from portfolio_optimiser.validator import proposal_for
project = load_reference_projects()[0]
reply = proposal_for(project, ["05.2", "03.1"], claimed_saving_nok=200_000).model_dump_json()
docs = tmp_path / "docs"
docs.mkdir()
(docs / "kilde.md").write_text("Cost saving measure candidates for the project.\n", "utf-8")
result = await run_project(
project.id,
"local",
docs_dir=str(docs),
store=VerdictStore(verdicts=[]),
client_factory=scripted_factory({"proposer": reply, "checker": _CHECKER}, []),
mandate=Mandate(
objective="o", approaches=(Approach(id="a1", label="l"),), allow_own_proposals=False
),
)
assert _statuses(result) == {"a1": "validated"}