Replace the exact document counts of earlier example bases (and the per-base counts in the sources-format note) with general wording or N-of-N in prose, comments and docstrings. Percentages and numerators stay; no constant, assertion or test data changes. 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 a few hundred documents, ``1.10.4`` in 1 of a few thousand). 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;
|
|
* ``1.10.4`` was NONE of the process catalogue's declared ``prosessnr`` and ``seksjon`` values:
|
|
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"]
|