"""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 survive in the recorded outboxes and are this arm's known positives: * ``10.4`` — tunnel-hauglia round 3, base ``vegnormal-n500-2024``, ``validated``; * ``1.10.4`` — lindaas P17b, base ``vegnormal-r761-2025``, ``validated``. Both are GROUNDED in P7's sense (they occur verbatim in the input) and neither is 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 vegnormal 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: * n500 declares ``seksjon: 10.4.1`` … ``10.4.4`` and ``req_number: Krav 10.4.3—2``. The bare ``10.4`` is declared NOWHERE — it is a section PREFIX; * r761 declares 2 727 ``prosessnr`` and 2 753 ``seksjon``. ``1.10.4`` is NONE of them: it occurs once, as prose, in "Krav til materialer skal være iht. vegnormal N200 Vegbygging kap. 1.10.4". 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 one context set built on real process codes: all five of ``contexts/kontrakt-sorasen-2027``'s codes are declared ``prosessnr`` and pass. Under the ordered rule every one of them would have been refused on an unanchored r761 run, and the set's positive arms would have become unmeasurable — the R761 risk the order names, arriving through the door it was pointed away from. Measured over EVERY code of round 3 and P17b (24 codes, 10 runs): exactly two are requirement-shaped, they are the two known positives, and the replay flips exactly those two. What each arm pins: (a) known positive — the ``10.4`` proposal, replayed against the base it actually ran on, is ``rejected``, and the reason names the denominator; (b) known positive — ditto ``1.10.4`` on r761; (c) known negative — a code the base DOES declare (sorasen's real ``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 import os from pathlib import Path import pytest 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, ) _DEFAULT_BUNDLE_ROOT = Path.home() / "repos" / "vegnormal-okf" / "build" / "ferdig" _ROUND3 = Path("scratchpad/p19-stress/tunnel-hauglia-2027") _P17B = Path("scratchpad/p17b-multibase/lindaas") def _base(name: str) -> Path: root = Path(os.environ.get("PORTFOLIO_VEGNORMAL_ROOT", str(_DEFAULT_BUNDLE_ROOT))) if not (root / name).is_dir(): pytest.skip(f"knowledge base {name!r} is not mounted under {root}") return root / name 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 _recorded(path: Path) -> SavingsProposal: if not path.is_file(): pytest.skip(f"the recorded artefact {path} is not present in this checkout") return SavingsProposal.model_validate(json.loads(path.read_text(encoding="utf-8"))["proposal"]) 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_the_section_number_that_reached_validated_on_n500_is_refused() -> None: """(a) tunnel-04's ``10.4``, replayed against the base that run actually opened.""" proposal = _recorded(_ROUND3 / "tunnel-hauglia-2027-04-a4-enhetspris-ventilator-proposal.json") assert [i.code for i in proposal.affected_items] == ["10.4"] outcome = validate_proposal(proposal, baseline=None, grounding=_grounding_over("n500-2024")) assert isinstance(outcome, Rejection) assert "'10.4'" in outcome.reason assert "not one of the 365 this knowledge base declares" in outcome.reason def test_the_process_number_that_reached_validated_on_r761_is_refused() -> None: """(b) lindaas a4's ``1.10.4`` — a chapter of ANOTHER standard, quoted in one r761 document.""" proposal = _recorded(_P17B / "lindaas-01-vegnormal-r761-2025-a4-indeksregulering-proposal.json") assert [i.code for i in proposal.affected_items] == ["1.10.4"] outcome = validate_proposal(proposal, baseline=None, grounding=_grounding_over("r761-2025")) assert isinstance(outcome, Rejection) assert "not one of the 2765 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/kontrakt-sorasen-2027``'s own first code and a REAL declared ``prosessnr`` of R761. Under the ordered rule it would have been refused; it must not be. """ grounding = _grounding_over("r761-2025") 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_sorasen_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/kontrakt-sorasen-2027/mandate.json").read_text(encoding="utf-8") )["approaches"] for code in approach.get("affected_codes", []) ] vocabulary = _grounding_over("r761-2025").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/kontrakt-sorasen-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("r761-2025")) 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 ASFALTDEKKER") 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", "impulsventilator"]) == { "12.1": "identifier", "SHA-01": "identifier", "impulsventilator": "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"