Say "referansesti" for the reference-project path in the P7 note, and replace the declared-number counts of earlier example material with the shipped synthetic catalogue's own (300 prosessnr, 306 declared numbers) or a general wording. Comments, docstrings and prose only; no constant or assertion changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
212 lines
9.7 KiB
Python
212 lines
9.7 KiB
Python
"""P22 DEL A — stage 0's unknown-code refusal NAMES the codes the project actually buys.
|
|
|
|
MEASURED (P21 stress round 5, re-measured at the head of økt 126): all 26 rejections across the
|
|
six paid runs read ``unknown cost code '<invention>': not in project P's cost baseline (5 known
|
|
codes)``, and **0 of 20** approaches validated (round 4: 4 of 20). The model invented 26 names
|
|
such as ``RIGG01`` and ``VENTIL_IMP``. It
|
|
could not have done otherwise: the price schedule reaches the VALIDATOR and never the proposer,
|
|
and the refusal states the COUNT of known codes, not one code's name. Step 5 feeds that sentence
|
|
verbatim into the next attempt's prompt — and "you guessed wrong, there are five right answers"
|
|
carries nothing to correct towards.
|
|
|
|
The contrast lives in this repo already. The MAGNITUDE half of the same stage NAMES the baseline
|
|
value, and that is the half that let the loop converge in økt 94: the proposer found each correct
|
|
figure because the refusal told it what the figure was.
|
|
|
|
**The window is a fixed COUNT, never a share** (the catalogue-excerpt rule, `_CATALOGUE_EXCERPT_
|
|
CHARS`), and it counts CODES rather than characters for a measured reason: a character cut can
|
|
sever a code mid-name and hand the proposer an identifier that exists nowhere — the
|
|
``_index_excerpt`` rule inverted, where a path that never existed is worse than no path. A count
|
|
window can only ever emit whole codes.
|
|
|
|
Measured sizes, with denominators (økt 126): every cost baseline anywhere in this repo or its
|
|
measured corpora was at most SIX codes (the five context sets of the time: 5 · 5 · 5 · 5 · 6; the
|
|
two shipped
|
|
``shared/examples`` baselines: 1 each; MAJOR-4's derivation of the synthetic K2 price schedule: 3),
|
|
and the largest REAL delivered price schedule measured is K2's ``prissammenstilling-sheet-1.md`` at
|
|
14 priced rows of 118 lines. Nothing measured reaches the window; it exists for the unmeasured
|
|
schedule of an agreement priced BY process number, where a process catalogue can declare hundreds
|
|
of them (the shipped synthetic catalogue: 300).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Final
|
|
|
|
from portfolio_optimiser.generate import _build_messages
|
|
from portfolio_optimiser.ir import (
|
|
AffectedItem,
|
|
CostBaseline,
|
|
CostBaselineLine,
|
|
SavingsProposal,
|
|
)
|
|
from portfolio_optimiser.reference_domain import Project
|
|
from portfolio_optimiser.validator import (
|
|
_KNOWN_CODE_WINDOW,
|
|
Rejection,
|
|
ValidatedProposal,
|
|
rejection_stage,
|
|
validate_proposal,
|
|
)
|
|
|
|
_SMALL: Final = ("RIGG", "LISENS", "MASSE", "FROST", "SKILT")
|
|
|
|
|
|
def _baseline(codes: tuple[str, ...], project_id: str = "proj") -> CostBaseline:
|
|
return CostBaseline(
|
|
project_id=project_id,
|
|
items={c: CostBaselineLine(code=c, quantity=100.0, unit_cost=1000.0) for c in codes},
|
|
)
|
|
|
|
|
|
def _proposal(code: str, *, quantity: float = 100.0, unit_cost: float = 1000.0) -> SavingsProposal:
|
|
return SavingsProposal(
|
|
project_id="proj",
|
|
measure="Reduce scope",
|
|
affected_items=[AffectedItem(code=code, quantity=quantity, unit_cost=unit_cost)],
|
|
claimed_saving_nok=1000.0,
|
|
assumptions={},
|
|
)
|
|
|
|
|
|
def _refusal(codes: tuple[str, ...], guess: str = "INVENTED") -> str:
|
|
result = validate_proposal(_proposal(guess), baseline=_baseline(codes))
|
|
assert isinstance(result, Rejection), "an invented code must never reach validated"
|
|
return result.reason
|
|
|
|
|
|
# --- (a) known-POSITIVE: the refusal names them --------------------------------------------------
|
|
|
|
|
|
def test_the_refusal_names_every_code_a_small_schedule_carries() -> None:
|
|
"""RED before DEL A: the refusal carried only ``(5 known codes)``. It now names all five, so
|
|
the sentence Step 5 feeds forward is CORRECTABLE. The denominator stays — a reader must be
|
|
able to tell how many exist from the same sentence that lists them."""
|
|
reason = _refusal(_SMALL)
|
|
for code in _SMALL:
|
|
assert code in reason, f"{code!r} must be named: {reason}"
|
|
assert "5 known codes" in reason, reason
|
|
|
|
|
|
# --- (b) known-NEGATIVE: the gate can still FELL, and stays silent when it should -----------------
|
|
|
|
|
|
def test_a_code_the_project_really_buys_still_validates() -> None:
|
|
"""The control the order demands alongside the positive: a corrected gate can still be untrue
|
|
for an independent reason. A proposal on a REAL line validates, so the naming above is caused
|
|
by the code being absent — not by a stage that now rejects everything."""
|
|
result = validate_proposal(_proposal("RIGG"), baseline=_baseline(_SMALL))
|
|
assert isinstance(result, ValidatedProposal)
|
|
|
|
|
|
def test_the_magnitude_half_does_not_grow_a_code_list() -> None:
|
|
"""A REAL code at an invented magnitude falls on the OTHER half of this stage, which already
|
|
names the baseline value. Listing the schedule there would be noise on a sentence that is
|
|
already correctable — and would make the two halves indistinguishable to a reader."""
|
|
result = validate_proposal(_proposal("RIGG", unit_cost=5000.0), baseline=_baseline(_SMALL))
|
|
assert isinstance(result, Rejection)
|
|
assert "tolerance around the baseline" in result.reason
|
|
assert "known codes" not in result.reason, result.reason
|
|
|
|
|
|
# --- (c)/(d) the window is BOUND, and it is a window and not a share ------------------------------
|
|
|
|
|
|
def test_a_large_schedule_is_bound_by_a_fixed_window() -> None:
|
|
"""A schedule priced by process number can carry thousands of lines. The list is bound at
|
|
``_KNOWN_CODE_WINDOW`` codes, the denominator still states how many exist, and the cut is
|
|
ANNOUNCED rather than left for the reader to subtract (``index_truncated``'s rule)."""
|
|
many = tuple(f"P{i:04d}" for i in range(500))
|
|
reason = _refusal(many)
|
|
named = [c for c in many if c in reason]
|
|
assert len(named) == _KNOWN_CODE_WINDOW, f"named {len(named)}, want {_KNOWN_CODE_WINDOW}"
|
|
assert "500 known codes" in reason, reason
|
|
assert f"first {_KNOWN_CODE_WINDOW}" in reason, reason
|
|
|
|
|
|
def test_the_window_is_a_count_and_not_a_share_of_the_schedule() -> None:
|
|
"""A share scales with the schedule again, only with a smaller constant — the exact regression
|
|
the catalogue excerpt's fixed window exists for. Two schedules an order of magnitude apart name
|
|
the SAME number of codes."""
|
|
small_named = sum(
|
|
1
|
|
for c in tuple(f"P{i:04d}" for i in range(200))
|
|
if c in _refusal(tuple(f"P{i:04d}" for i in range(200)))
|
|
)
|
|
big_named = sum(
|
|
1
|
|
for c in tuple(f"P{i:04d}" for i in range(2000))
|
|
if c in _refusal(tuple(f"P{i:04d}" for i in range(2000)))
|
|
)
|
|
assert small_named == big_named == _KNOWN_CODE_WINDOW
|
|
|
|
|
|
def test_a_schedule_exactly_at_the_window_is_not_announced_as_cut() -> None:
|
|
"""Omission, never a lie in either direction: a schedule that FITS is not marked truncated and
|
|
gets its whole list (``index_truncated``'s positive half)."""
|
|
exact = tuple(f"P{i:04d}" for i in range(_KNOWN_CODE_WINDOW))
|
|
reason = _refusal(exact)
|
|
assert all(c in reason for c in exact)
|
|
assert "first " not in reason, reason
|
|
|
|
|
|
# --- (e)/(f) the codes it hands back are the project's own, whole and in its own order ------------
|
|
|
|
|
|
def test_the_named_codes_are_the_schedules_own_order_never_sorted() -> None:
|
|
"""The schedule's order is the PROJECT's order. A sort would invent a ranking the project never
|
|
stated, and the first window would then be an alphabetical accident rather than the head of the
|
|
document the operator wrote."""
|
|
unsorted = ("ZZ-last", "AA-first", "MM-middle") + tuple(f"P{i:03d}" for i in range(50))
|
|
reason = _refusal(unsorted)
|
|
named = [c for c in unsorted if c in reason]
|
|
assert named[:3] == ["ZZ-last", "AA-first", "MM-middle"], named[:3]
|
|
|
|
|
|
def test_every_named_token_resolves_to_a_real_baseline_line() -> None:
|
|
"""The ``_index_excerpt`` property, measured rather than assumed: each code the refusal hands
|
|
back is a WHOLE key of the baseline. A character-sliced window could emit ``'P004`` and send
|
|
the proposer after an identifier that exists nowhere."""
|
|
many = tuple(f"code-{i:03d}-long-enough-to-slice" for i in range(100))
|
|
reason = _refusal(many)
|
|
listed = reason.split("first ")[1].split(": ", 1)[1].rstrip(")")
|
|
tokens = [t.strip().strip("'") for t in listed.split(", ")]
|
|
assert len(tokens) == _KNOWN_CODE_WINDOW, tokens
|
|
for token in tokens:
|
|
assert token in many, f"{token!r} is not a baseline code"
|
|
|
|
|
|
# --- (g) the coupling P21's "a4 5/5 on stage0-baseline" rests on ----------------------------------
|
|
|
|
|
|
def test_the_longer_sentence_is_still_labelled_stage0() -> None:
|
|
"""``rejection_stage`` keys on ``"cost baseline ("``. Had the new clause moved that substring,
|
|
every stage-0 refusal would have been relabelled ``"other"`` in silence, and P21's headline
|
|
measurement (``must_refuse`` 5/5, all on ``stage0-baseline``) would have read as a regression
|
|
caused by the fix meant to strengthen it."""
|
|
assert rejection_stage(_refusal(_SMALL)) == "stage0-baseline"
|
|
|
|
|
|
# --- (h) Step 5 carries it to the place it has to reach ------------------------------------------
|
|
|
|
|
|
def test_the_named_codes_reach_the_next_attempts_prompt() -> None:
|
|
"""The whole point: the refusal is only correctable if it reaches the proposer. Step 5 feeds
|
|
the reason VERBATIM into the next attempt, so the codes ride for free — this arm measures that
|
|
they arrive, rather than trusting that they do."""
|
|
reason = _refusal(_SMALL)
|
|
messages = _build_messages(
|
|
Project(
|
|
id="proj",
|
|
name="Proj",
|
|
description="",
|
|
currency="NOK",
|
|
cost_items=(),
|
|
docs_dir="",
|
|
),
|
|
"context",
|
|
Rejection(proposal=_proposal("INVENTED"), reason=reason),
|
|
)
|
|
prompt = "\n".join(m.text for m in messages)
|
|
for code in _SMALL:
|
|
assert code in prompt, f"{code!r} never reached the prompt"
|