feat(p21): the PROJECT carries the price, so a run against a road normal can be anchored
Four paid stress rounds ran entirely UN-ANCHORED, all of them, because the one file loader reads cost-baseline.json out of the BUNDLE 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 tells an invented cost line from a line this project actually buys -- was skipped in every single run, so "validated" could not mean what it says. P20 G1/G2 measured real R761 process numbers (12.11 three times on Soraasen, 1.1.1 on Lindaas) validating with amounts nobody had anywhere. --cost-baseline FILE is PM decision (e), taken over the three alternatives P20 wrote down. A LOADED object, never a path (prepass_payload's rule): the CLI owns the file and loads it ONCE, so the notice, the stamp and every base of an --across-bundle pass all descend from one read. ONE parse, two doors -- load_cost_baseline delegates to load_cost_baseline_file -- while safe_resolve stays on the bundle door alone, because a project's own schedule is legitimately outside every base. No tolerant twin: this path exists only because an operator NAMED a file. DEL B: five anchored context sets, a1-a3 with their line and a4 with none, so stage 0 is what catches the falsification arm. THE ORDER'S OWN ARM (h) WAS FELLED BY MEASUREMENT: "no baseline code is a requirement number the base declares" is measured 0 of 4 on the project-coded sets and 5 of 5 on kontrakt-sorasen -- which is what R761 Prosesskoden IS, a bill of quantities priced BY process code. The complement keeps both, and the order's own mutation still bites. DEL B3: the judge reports anchored (off the run's own stamp), priced per row, and WHICH falsifier caught the falsification arm. Load-bearing MEASURED, five mutations all red against the WHOLE suite, green control 1850/5 (from 1809/5, superset, 0 removed), golden byte-unchanged: A3(i) the flag is read but the baseline is unused (3 red) . A3(ii) only the first base gets it (1) . A3(iii) report_forbidden drops it (1) . B2(i) a4 gets a line (1, arm (g) alone) . B2(ii) a code swapped to 12.11 (2, arms (f) and (h)). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
587480f050
commit
7b4f85d77c
20 changed files with 1259 additions and 15 deletions
|
|
@ -209,3 +209,64 @@ def test_the_classification_is_reported_and_is_the_gates_own() -> None:
|
|||
verdict = validate_proposal(_proposal(code), grounding=Grounding(_OFFERING + (code,)))
|
||||
refused = isinstance(verdict, Rejection) and "has no identifier form" in verdict.reason
|
||||
assert refused == (kind == "prose"), (code, kind, verdict)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# P21 B3: ``rejection_stage`` — which falsifier wrote a reason. A REPORT, never a gate: nothing
|
||||
# branches on it, so an unrecognised sentence costs a label rather than a verdict. It lives beside
|
||||
# the sentences it keys on, so the classifier and the wordings cannot drift apart (kø-(p)).
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_p21_rejection_stage_names_each_stage_from_its_own_sentence() -> None:
|
||||
from portfolio_optimiser.validator import rejection_stage
|
||||
|
||||
assert (
|
||||
rejection_stage("unknown cost code 'X': not in project p's cost baseline (3 known codes)")
|
||||
== "stage0-baseline"
|
||||
)
|
||||
assert (
|
||||
rejection_stage(
|
||||
"quantity 5 for cost code 'X' is outside the 5.0% tolerance around the baseline "
|
||||
"quantity 9"
|
||||
)
|
||||
== "stage0-baseline"
|
||||
)
|
||||
assert (
|
||||
rejection_stage("ungrounded identifier 'X': it appears nowhere in the input (9 chars)")
|
||||
== "stage0b-grounding"
|
||||
)
|
||||
assert rejection_stage("claimed saving 9 exceeds P90 feasible 4") == "stage4-p90"
|
||||
assert (
|
||||
rejection_stage("claimed saving 9 exceeds the nominal feasible 4 at the items' stated")
|
||||
== "stage4b-nominal"
|
||||
)
|
||||
assert (
|
||||
rejection_stage("claimed 9 exceeds the energy_efficiency method cap 4 (stricter)")
|
||||
== "stage5-method-cap"
|
||||
)
|
||||
# The honest answer for a sentence this module did not write — a label, never a verdict.
|
||||
assert rejection_stage("something else entirely") == "other"
|
||||
|
||||
|
||||
def test_p21_rejection_stage_is_keyed_on_the_sentences_the_validator_emits() -> None:
|
||||
"""The control: the markers are not a private paraphrase but the text stage 0 really writes.
|
||||
|
||||
Without it the classifier could key on wording nothing emits and every arm above would still
|
||||
be green — the vacuous-gate class, on a reporter.
|
||||
"""
|
||||
from portfolio_optimiser.ir import AffectedItem, CostBaseline, CostBaselineLine, SavingsProposal
|
||||
from portfolio_optimiser.validator import Rejection, rejection_stage, validate_proposal
|
||||
|
||||
baseline = CostBaseline(
|
||||
project_id="proj", items={"REAL-1": CostBaselineLine(quantity=10.0, unit_cost=100.0)}
|
||||
)
|
||||
proposal = SavingsProposal(
|
||||
project_id="proj",
|
||||
measure="m",
|
||||
affected_items=[AffectedItem(code="FAKE-1", quantity=10.0, unit_cost=100.0)],
|
||||
claimed_saving_nok=100.0,
|
||||
)
|
||||
outcome = validate_proposal(proposal, baseline=baseline)
|
||||
assert isinstance(outcome, Rejection)
|
||||
assert rejection_stage(outcome.reason) == "stage0-baseline"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue