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>
355 lines
16 KiB
Python
355 lines
16 KiB
Python
"""P20 DEL B — a clause number is not a price, and the base's own vocabulary is what says so.
|
|
|
|
**What was measured.** Three paid rounds and one multi-base pass carried FOUR ``validated``
|
|
proposals whose cost code was a chapter number of a standard. Two survived in the recorded
|
|
outboxes and were this arm's known positives:
|
|
|
|
* ``10.4`` — a stress round on a requirements base, ``validated``;
|
|
* ``1.10.4`` — the multi-base pass, on the process catalogue, ``validated``.
|
|
|
|
Both were GROUNDED in P7's sense (they occur verbatim in the input) and neither was INERT in
|
|
P18/B1's sense (``10.4`` in 12 of 274 documents, ``1.10.4`` in 1 of 2 756). Stage 0 never ran:
|
|
no requirements base ships a cost baseline. Nothing in the gate could say what they are.
|
|
|
|
**THE ORDER'S OWN RULE WAS FELLED BY MEASUREMENT BEFORE ANYTHING WAS BUILT ON IT.** B1 reads: a
|
|
code is a requirement when it has form 2 or 3 AND "står som ``req_number``/``prosessnr`` i
|
|
toppnivå-frontmatter i minst ett av grunnlagets dokumenter" — refuse that. Measured 15.09:
|
|
|
|
* the requirements base declared ``seksjon: 10.4.1`` … ``10.4.4`` and ``req_number: Krav
|
|
10.4.3—2``. The bare ``10.4`` was declared NOWHERE — it is a section PREFIX;
|
|
* the process catalogue declared 2 727 ``prosessnr`` and 2 753 ``seksjon``. ``1.10.4`` was NONE of
|
|
them: it occurred once, as prose, a chapter reference into ANOTHER standard.
|
|
|
|
The ordered rule therefore fires on NEITHER of its own known positives. The COMPLEMENT fires on
|
|
BOTH, and it closes a hole ``_ground_against_input`` already admits in writing — "it fails OPEN …
|
|
on a coincidental match". For one shape, a clause number, the base hands us the vocabulary needed
|
|
to tell a real reference from a coincidence, and that is the rule built here.
|
|
|
|
The complement is also what SPARES the context set built on the catalogue's own process codes:
|
|
all five of ``contexts/driftsavtale-2027``'s codes are declared ``prosessnr`` and pass. Under the
|
|
ordered rule every one of them would have been refused on an unanchored run on the catalogue, and
|
|
the set's positive arms would have become unmeasurable — the risk the order names (a process
|
|
number is both a clause and a settlement post), arriving through the door it was pointed away from.
|
|
|
|
Measured over EVERY code of that round and the multi-base pass (24 codes, 10 runs): exactly two
|
|
were requirement-shaped, they were the two known positives, and the replay flipped exactly those
|
|
two. Those recordings were made against corpora this repository no longer carries, so (a) and (b)
|
|
are now CONSTRUCTED in the same two shapes against the package's example bases: ``4.2.5`` — a
|
|
section PREFIX in ``driftskrav-2027`` (it declares ``seksjon: 4.2.5.1`` and ``Krav 4.2.5.1—1`` …
|
|
``—3``; the bare ``4.2.5`` stands in 3 of 306 documents and is no document's own number) — and
|
|
``1.10.4`` — a clause of another document quoted once, as prose, in ``prosesskatalog-2027``.
|
|
|
|
What each arm pins:
|
|
|
|
(a) known positive — the section prefix ``4.2.5``, unanchored against the requirements base, is
|
|
``rejected``, and the reason names the denominator;
|
|
(b) known positive — ditto ``1.10.4`` on the process catalogue;
|
|
(c) known negative — a code the base DOES declare (driftsavtale's ``12.1``) still validates;
|
|
(d) known negative — the gate is OFF when the run is anchored, even for a clause-shaped code;
|
|
(e) the generality guard — an input that declares no reference numbers at all cannot trip the rule,
|
|
which is what leaves every pre-P20 fixture untouched rather than exempted;
|
|
(f) K2's identifier forms are untouched: ``SHA-01`` is not requirement-shaped;
|
|
(g) ``classify_codes``' third value, and its denominator-free reading (``grounding=None``) that
|
|
``stress.py`` re-derives with;
|
|
(h) the vocabulary travels WITH the text through ``_grounding_text``, so the gate the generation
|
|
loop runs sees what the run composed;
|
|
(i) a run composes the vocabulary from the base it opened — asserted end-to-end through
|
|
``run_project``, not on the composer.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from portfolio_optimiser import frozen_bundles
|
|
|
|
from portfolio_optimiser import okf
|
|
from portfolio_optimiser.generate import _grounding_text
|
|
from portfolio_optimiser.ir import CostBaseline, SavingsProposal
|
|
from portfolio_optimiser.reference_domain import Project
|
|
from portfolio_optimiser.validator import (
|
|
Grounding,
|
|
Rejection,
|
|
ValidatedProposal,
|
|
classify_codes,
|
|
has_requirement_form,
|
|
validate_proposal,
|
|
)
|
|
|
|
|
|
def _base(name: str) -> Path:
|
|
"""The FROZEN copy this repository pins, resolved at call time.
|
|
|
|
Absence SKIPS (a user's own store, named by ``PORTFOLIO_FROZEN_BUNDLES``, may not hold it),
|
|
drift is allowed to propagate and FAIL — a measurement of the wrong corpus is not a missing one.
|
|
"""
|
|
try:
|
|
return frozen_bundles.bundle_dir(name)
|
|
except frozen_bundles.FrozenBundleMissing as exc:
|
|
pytest.skip(str(exc))
|
|
|
|
|
|
def _grounding_over(name: str) -> Grounding:
|
|
"""The delivered base exactly as ``run_project`` composes it — documents AND vocabulary."""
|
|
bundle = okf.navigate_bundle(str(_base(name)))
|
|
return Grounding(
|
|
documents=tuple(
|
|
"\n".join([f.name, *f.frontmatter.values(), f.body]) for f in bundle.context_files
|
|
),
|
|
declared_references=tuple(
|
|
ref for f in bundle.context_files for ref in okf.declared_reference_numbers(f)
|
|
),
|
|
)
|
|
|
|
|
|
def _proposal(code: str, *, saving: float = 1000.0) -> SavingsProposal:
|
|
return SavingsProposal(
|
|
project_id="p",
|
|
measure="m",
|
|
affected_items=[{"code": code, "quantity": 10.0, "unit_cost": 1000.0}],
|
|
claimed_saving_nok=saving,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------------- known positives
|
|
|
|
|
|
def test_a_section_prefix_of_the_requirements_base_is_refused() -> None:
|
|
"""(a) The ``10.4`` shape: a section PREFIX that stands in the base, is not inert, and is no
|
|
document's own number. The CONTROL is what makes the refusal this rule's and not another
|
|
stage's: the prefix IS in the text, in too few documents to be inert, and not declared."""
|
|
grounding = _grounding_over("driftskrav-2027")
|
|
assert "4.2.5" in grounding.text and "4.2.5" not in grounding.reference_vocabulary
|
|
assert grounding.document_frequency("4.2.5") == 3
|
|
outcome = validate_proposal(_proposal("4.2.5"), baseline=None, grounding=grounding)
|
|
assert isinstance(outcome, Rejection)
|
|
assert "'4.2.5'" in outcome.reason
|
|
assert "not one of the 279 this knowledge base declares" in outcome.reason
|
|
|
|
|
|
def test_a_clause_of_another_document_quoted_in_the_catalogue_is_refused() -> None:
|
|
"""(b) The ``1.10.4`` shape: a clause of ANOTHER document, quoted once in the catalogue."""
|
|
grounding = _grounding_over("prosesskatalog-2027")
|
|
assert grounding.document_frequency("1.10.4") == 1
|
|
assert "1.10.4" not in grounding.reference_vocabulary
|
|
outcome = validate_proposal(_proposal("1.10.4"), baseline=None, grounding=grounding)
|
|
assert isinstance(outcome, Rejection)
|
|
assert "not one of the 306 this knowledge base declares" in outcome.reason
|
|
|
|
|
|
# ---------------------------------------------------------------------------------- known negatives
|
|
|
|
|
|
def test_a_process_code_the_base_declares_still_validates() -> None:
|
|
"""(c) The arm that keeps this a rule about the corpus and not about shapes.
|
|
|
|
``12.1`` is ``contexts/driftsavtale-2027``'s own first code and a declared ``prosessnr`` of
|
|
the catalogue. Under the ordered rule it would have been refused; it must not be.
|
|
"""
|
|
grounding = _grounding_over("prosesskatalog-2027")
|
|
assert "12.1" in grounding.reference_vocabulary
|
|
outcome = validate_proposal(_proposal("12.1"), baseline=None, grounding=grounding)
|
|
assert isinstance(outcome, ValidatedProposal), getattr(outcome, "reason", "")
|
|
|
|
|
|
def test_every_driftsavtale_code_is_in_the_bases_vocabulary() -> None:
|
|
"""(c) The whole context set, not one sample: five real codes, five declared numbers."""
|
|
codes = [
|
|
code
|
|
for approach in json.loads(
|
|
Path("contexts/driftsavtale-2027/mandate.json").read_text(encoding="utf-8")
|
|
)["approaches"]
|
|
for code in approach.get("affected_codes", [])
|
|
]
|
|
vocabulary = _grounding_over("prosesskatalog-2027").reference_vocabulary
|
|
shaped = [c for c in codes if has_requirement_form(c)]
|
|
assert len(shaped) == 5, shaped
|
|
assert [c for c in shaped if c not in vocabulary] == []
|
|
|
|
|
|
def test_the_fasit_references_are_classified_requirement() -> None:
|
|
"""(g) known negative (c) of the order: a fasit reference IS a requirement, and says so."""
|
|
fasit = json.loads(Path("contexts/driftsavtale-2027/fasit.json").read_text(encoding="utf-8"))
|
|
refs = sorted({c["ref"] for entry in fasit["must_cite"] for c in entry["concepts"]})
|
|
forms = classify_codes(refs, _grounding_over("prosesskatalog-2027"))
|
|
assert set(forms.values()) == {"requirement"}, forms
|
|
|
|
|
|
def test_an_anchored_run_is_untouched_by_the_rule() -> None:
|
|
"""(d) Stage 0 has already ruled; the weaker stage must not overrule the stronger."""
|
|
grounding = Grounding(documents=("12.9 is a clause",), declared_references=("12.1", "12.2"))
|
|
baseline = CostBaseline(project_id="p", items={"12.9": {"quantity": 10.0, "unit_cost": 1000.0}})
|
|
outcome = validate_proposal(_proposal("12.9"), baseline=baseline, grounding=grounding)
|
|
assert isinstance(outcome, ValidatedProposal), getattr(outcome, "reason", "")
|
|
# The control: the SAME code and the SAME text, unanchored, is refused.
|
|
unanchored = validate_proposal(_proposal("12.9"), baseline=None, grounding=grounding)
|
|
assert isinstance(unanchored, Rejection)
|
|
|
|
|
|
def test_an_input_that_declares_no_reference_numbers_cannot_trip_the_rule() -> None:
|
|
"""(e) The generality guard — and the reason every pre-P20 fixture is untouched."""
|
|
grounding = Grounding(documents=("a document mentioning 12.9 once",))
|
|
assert grounding.reference_vocabulary == frozenset()
|
|
outcome = validate_proposal(_proposal("12.9"), baseline=None, grounding=grounding)
|
|
assert isinstance(outcome, ValidatedProposal), getattr(outcome, "reason", "")
|
|
|
|
|
|
def test_a_cost_line_identifier_is_not_requirement_shaped() -> None:
|
|
"""(f) K2's 50 identifiers and this repo's own code: shape, measured."""
|
|
assert not has_requirement_form("SHA-01")
|
|
assert not has_requirement_form("ENERGI-TOTAL-EL")
|
|
assert not has_requirement_form("65 LAGRINGSSYSTEMER")
|
|
assert has_requirement_form("10.4") and has_requirement_form("Krav 4.1.2—1")
|
|
|
|
|
|
def test_classify_codes_without_a_grounding_is_the_pre_p20_answer() -> None:
|
|
"""(g) ``stress.py`` re-derives for runs written before the field existed."""
|
|
assert classify_codes(["12.1", "SHA-01", "nødstrømsaggregat"]) == {
|
|
"12.1": "identifier",
|
|
"SHA-01": "identifier",
|
|
"nødstrømsaggregat": "prose",
|
|
}
|
|
grounding = Grounding(documents=("x",), declared_references=("12.1",))
|
|
assert classify_codes(["12.1"], grounding) == {"12.1": "requirement"}
|
|
|
|
|
|
# ---------------------------------------------------------------------------------- the wiring
|
|
|
|
|
|
def test_the_vocabulary_travels_with_the_text_into_the_generation_gate() -> None:
|
|
"""(h) ``_grounding_text`` composes the run's three sources; the vocabulary must survive it."""
|
|
delivered = Grounding(documents=("d",), declared_references=("12.1",))
|
|
project = Project(
|
|
id="p", name="n", description="d", currency="NOK", cost_items=(), docs_dir="."
|
|
)
|
|
composed = _grounding_text(project, None, delivered)
|
|
assert composed.reference_vocabulary == frozenset({"12.1"})
|
|
|
|
|
|
def test_a_run_composes_the_vocabulary_from_the_base_it_opened(tmp_path: Path) -> None:
|
|
"""(i) End-to-end through ``run_project``: the stamp says ``requirement`` for a declared code.
|
|
|
|
Asserted on the ARTEFACT a run leaves, never on the composer — a vocabulary wired nowhere would
|
|
satisfy every arm above and none of this one.
|
|
"""
|
|
import asyncio
|
|
|
|
from agent_framework import BaseChatClient
|
|
|
|
from portfolio_optimiser.run import run_project
|
|
from portfolio_optimiser.simulation import ScriptedChatClient
|
|
|
|
base = tmp_path / "mini"
|
|
base.mkdir()
|
|
(base / "index.md").write_text(
|
|
"---\nbundle_id: mini\n---\n\n- [Krav](krav.md) — one clause.\n", encoding="utf-8"
|
|
)
|
|
(base / "krav.md").write_text(
|
|
"---\ntype: Krav\ntitle: Krav 4.1.2-1\nprosessnr: '12.1'\n---\n\nEn kostlinje 12.1.\n",
|
|
encoding="utf-8",
|
|
)
|
|
(base / "validator-input.json").write_text(
|
|
json.dumps({"project_id": "mini-p", "measure": "m", "affected_codes": ["12.1"]}),
|
|
encoding="utf-8",
|
|
)
|
|
reply = (
|
|
'{"measure":"m","affected_items":[{"code":"12.1","quantity":10,"unit_cost":1000}],'
|
|
'"claimed_saving_nok":1000}'
|
|
)
|
|
|
|
def factory(role: str) -> BaseChatClient:
|
|
return ScriptedChatClient(
|
|
"Reasoning holds.\nVERDICT: APPROVE" if role == "checker" else reply, role=role
|
|
)
|
|
|
|
result = asyncio.run(
|
|
run_project(
|
|
"mini-p",
|
|
"local",
|
|
docs_dir=str(base),
|
|
bundle_dir=str(base),
|
|
client_factory=factory,
|
|
)
|
|
)
|
|
assert result.provenance.code_forms == {"12.1": "requirement"}
|
|
assert result.provenance.validator_decision == "validated"
|
|
|
|
|
|
def test_each_approachs_artefact_carries_its_own_code_forms(tmp_path: Path) -> None:
|
|
"""(j) MEASURED on round 3 AND round 4: every per-approach artefact carried the SELECTED
|
|
proposal's codes.
|
|
|
|
``code_forms``' own comment says it is derived "off the proposal being stamped", and the
|
|
per-approach branch copied the run's stamp while overriding only ``validator_decision``. So an
|
|
artefact about approach 2 reported approach 1's codes — and ``stress.py``, which reads this
|
|
field before re-deriving, then produced an EMPTY ``prose_codes`` for every approach but the
|
|
first, because none of that approach's codes was a key in the map it was handed.
|
|
|
|
Run-level was the drift, not the intent: model, citations and token usage ARE the run's, and
|
|
they stay so.
|
|
"""
|
|
import asyncio
|
|
|
|
from agent_framework import BaseChatClient
|
|
|
|
from portfolio_optimiser.mandate import Approach, Mandate
|
|
from portfolio_optimiser.run import run_project
|
|
from portfolio_optimiser.simulation import ScriptedChatClient
|
|
|
|
base = tmp_path / "mini"
|
|
base.mkdir()
|
|
(base / "index.md").write_text(
|
|
"---\nbundle_id: mini\n---\n\n- [Krav](krav.md) — one clause.\n", encoding="utf-8"
|
|
)
|
|
(base / "krav.md").write_text(
|
|
"---\ntype: Krav\ntitle: Krav 4.1.2-1\nprosessnr: '12.1'\n---\n\nLines 12.1 and PRIS-EN.\n",
|
|
encoding="utf-8",
|
|
)
|
|
(base / "validator-input.json").write_text(
|
|
json.dumps({"project_id": "mini-p", "measure": "m", "affected_codes": ["12.1"]}),
|
|
encoding="utf-8",
|
|
)
|
|
outbox = tmp_path / "out"
|
|
|
|
# Each approach's prompt names its own label, so the selector answers with ITS OWN code.
|
|
def select(prompt: str, role: str) -> str:
|
|
if role == "checker":
|
|
return "Reasoning holds.\nVERDICT: APPROVE"
|
|
code = "PRIS-EN" if "SECOND" in prompt else "12.1"
|
|
return (
|
|
'{"measure":"m","affected_items":[{"code":"%s","quantity":10,"unit_cost":1000}],'
|
|
'"claimed_saving_nok":1000}' % code
|
|
)
|
|
|
|
def factory(role: str) -> BaseChatClient:
|
|
return ScriptedChatClient(reply_selector=select, role=role)
|
|
|
|
asyncio.run(
|
|
run_project(
|
|
"mini-p",
|
|
"local",
|
|
docs_dir=str(base),
|
|
bundle_dir=str(base),
|
|
client_factory=factory,
|
|
mandate=Mandate(
|
|
objective="o",
|
|
approaches=(
|
|
Approach(id="a1", label="FIRST", description="d"),
|
|
Approach(id="a2", label="SECOND", description="d"),
|
|
),
|
|
allow_own_proposals=False,
|
|
),
|
|
outbox_dir=str(outbox),
|
|
run_id="r1",
|
|
)
|
|
)
|
|
forms = {}
|
|
for name in ("a1", "a2"):
|
|
payload = json.loads((outbox / f"r1-{name}-proposal.json").read_text(encoding="utf-8"))
|
|
codes = [i["code"] for i in payload["proposal"]["affected_items"]]
|
|
forms[name] = (codes, payload["provenance"]["code_forms"])
|
|
assert forms["a1"] == (["12.1"], {"12.1": "requirement"}), forms["a1"]
|
|
assert forms["a2"] == (["PRIS-EN"], {"PRIS-EN": "identifier"}), forms["a2"]
|