llm-ingestion-okf/tests/test_default_bundle_pin.py
Kjell Tore Guttormsen 191de89f41 feat(propose,consume,tools): the type that declares nothing, and the prefix that is not a word
Three of round 9's four measured holes, each closed with a rule chosen on a
measurement rather than named as a limit.

`rtf` GIVES 0 SEGMENTS -> 6 of 6 AUTHORED TITLES over N = 4. The container has
no heading style, so the author's title is bold text. The grammar is markdown,
not `rtf`: the converter already writes that title as `**...**` in the same
output every office row produces, so no `rtf`-only heading form exists. Three
parameters were swept over 47 readable documents and ONE carried -- refusing a
line that ends in terminal punctuation takes false-positive lines from 9-12 to
1-2. A maximum title length (unlimited/40/60/80/120) and a
must-stand-between-blank-lines clause are both FLAT, so neither is in the rule.
The last false positive is closed by G1, the principle `_gate_outline` already
carries: recovery yields to declaration. False positives are then 0 of the 31
declaring documents by construction, and 0 of 27 on the corpus. Reach: 2 of 39
corpus documents, both `docx`, 0 of 33 `pdf` and 0 of 2 `xlsx`. Behind
`--bold-title`, default OFF pending the hit@8 measurement; the default bundle
is byte-identical without it.

BOTH ALTERNATIVES THE ORDER NAMED WERE MEASURED AND FELLED. A fourth hand-laid
fixture DECLARES heading styles in a stylesheet and the converter discards
them, emitting the same bold line -- so "read the declared headings out of the
markdown" has nothing to read. `rtf` -> `docx` -> markdown yields 0 ATX
headings on that same document, because the loss is in the `rtf` READER before
any writer sees the style. Fixtures are hand-laid in `make_k2_office.py` with
the fasit written first; they live in their own directory because Door B walks
a drop directory recursively and `k2-office/` reads its N off the listing.

THE PREFIX OVER-MATCH: THREE CANDIDATES MEASURED, ALL THREE FAILED ON ONE ROW.
Re-measured on the pinned 453-concept bundle with the control run first:
`under` occurs 79 times by equality and matches 172 by prefix, `undersjoisk` 0
and 172, `bilateral` 0 and 400 of 453, `standhaftig` 0 and 219. The two extra
known-negatives were FOUND, not chosen -- every 4-character prefix ranked by
document frequency, then a real word taken from the widest. A longer floor
(5-8), a coverage share (0.5-0.8) and a long-words-only floor (>= 8) each cost
row 1 its rank on the default bundle and the whole row on Arm B. Decomposed:
row 1's token `prisene` reaches its gold document through
`pris|sammenstilling` on four characters -- 0.57 of one word and 0.22 of the
other -- so the over-match and the wanted match are one mechanism.

THE FOURTH CANDIDATE IS THE ANSWER: the shared prefix must be a WORD the bundle
uses. `pris` is; `bila` and `stan` are not. `bilateral` 400 -> 0 and 512 -> 0,
`standhaftig` 219 -> 56 and 235 -> 33, every hit@8 row keeping rank 1 on BOTH
bundles. `undersjoisk` stops at 162 because `under` IS a word here -- a genuine
Norwegian morpheme, so that residual is a different answer, not a ceiling. ON
by default (`--no-stem-prefix`), pinned with its own known-negative on the
shipped bytes.

THE SHIM: a path importer holds the object `module_from_spec` made, and
`sys.modules[__name__] = _impl` never reaches it. Measured under both counting
methods -- 3 of 76 public names by `vars()`. One line copies the public names
into this file's globals; the dunder filter is load-bearing, because an
unfiltered copy overwrites `__name__` before the next line uses it as the alias
key. It restores attribute ACCESS and not patch-through, which is why the alias
stays. A CHANGELOG note under 0.7.0 and a shim docstring line say so, since
what the consumer asked for was the note.

Suite 1515 -> 1535.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 23:05:45 +02:00

224 lines
10 KiB
Python

"""The bundle the DEFAULT build produces, pinned where a regression goes red.
`tests/test_okf_consume.py` pinned hit@8 against the Arm B bundle alone -- the
configuration `okf build` stopped emitting on 2026-09-08. A published number
measured on a bundle nobody produces is a number that cannot regress, so the
guarantee it looks like was never held by anything.
This file pins the CURRENT default: `--outline-run 3 --table-grid --unit-fold
--drop-wrapped-outline --outline-gate --first-span-from-zero
--sheet-section-rows --keep-table-heading --close-span-gaps`, plus the reading
side's `tie_shared_rank`. Round 6 moved the first five on 2026-09-09, round 7
moved four more on 2026-09-10 and round 8 moved the last on 2026-09-11, each
after measuring hit@8 on exactly the bundle its own default produces.
The gold set is LOCAL-ONLY and stays that way: no question and no
`gold_document` is reproduced here, and a row is named by its INDEX, the way
`docs/2026-09-07-okf-konsumskill-maaling.md` already names them.
The bundle itself is a build artefact, not a fixture: it is 832 files of a
consumer's corpus and this repository is public. Absent, these tests SKIP with
the command that rebuilds it -- "not measured", never zero.
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROJECT_ROOT / "tools"))
import okf_consume # noqa: E402
import okf_consume_measure # noqa: E402
#: Built by:
#: okf build <corpus>/K2/trinn1 \
#: --bundle ~/corpora/okf-telling-20260829/K2-bundle-default-20260911 \
#: --bundle-id k2-trinn1-20260903 --okf-version 0.2
#: with no arm flag at all -- the package default, which is the point.
#:
#: Rebuilt 2026-09-09 for `--contents-name` (round 9). Digest, from inside the
#: bundle:
#: find . -type f -print0 | sort -z | xargs -0 shasum -a 256 | shasum -a 256
#: -> 21af4a1aa98315cf514c4cbc6b4a9b77ce63960224d6d7b31b34d55cc67fb2ad
#: (The previous default, `K2-bundle-default-20260911`, was
#: 8c93e5e3222577a2b3352ca83af980e403d3a571c3a467b83c3d8170b1df2b69 at 436
#: concepts and stays on disk.)
#: Two independent builds of it differ in NOTHING (`diff -rq`), including
#: `log.md`, which carries the corpus path and never the bundle's own.
#:
#: CONCEPT IDS MOVED IN THIS REBUILD, and not only because the count did.
#: Round 9 strips pandoc's `{#sheet-N}` / `{#slide-N}` anchor where a title is
#: formed, and a concept's filename is reduced FROM its title, so TWO ids on
#: this bundle are renamed:
#: del-ii-bilag-7-prisskjema/prissammenstilling-sheet-1 -> .../prissammenstilling
#: del-ii-bilag-0-dokumentliste-del-ii/ark1-sheet-1 -> .../ark1
#: The first is an id `portfolio-optimiser` has cited in writing. The rename
#: was authorised by the operator on 2026-09-09 after the exposure was counted:
#: 2 of 810 concepts on the previous default and 2 of 1108 on Arm B.
DEFAULT_BUNDLE = Path.home() / "corpora" / "okf-telling-20260829" / "K2-bundle-default-20260912"
GOLD_SET = PROJECT_ROOT / ".claude/projects/2026-09-07-okf-consume-prepass/hit-at-k-questions.json"
requires_default_bundle = pytest.mark.skipif(
not DEFAULT_BUNDLE.is_dir() or not GOLD_SET.is_file(),
reason=(
f"the default-configuration K2 bundle is not present at {DEFAULT_BUNDLE}. "
"NOT MEASURED, not zero: rebuild it with `okf build <corpus>/K2/trinn1 "
"--bundle <that path> --bundle-id k2-trinn1-20260903 --okf-version 0.2`"
),
)
#: Measured 2026-09-09 on the bundle above. The count moved 425 -> 436 with
#: `--sheet-section-rows --keep-table-heading`; `--first-span-from-zero` and
#: `--close-span-gaps` each moved it by NOTHING, which is the point of both --
#: they add no boundary, they only move a span's start or its end. Round 8's
#: rule closed 43 631 characters (2.51 % of the corpus) that were in no
#: segment, and the count was byte-for-byte the same 436.
#:
#: 436 -> 453 with round 9's `--contents-name`, which does add concepts: a run
#: of data rows is no longer read as a contents listing and discarded, so the
#: candidates it was taking with it survive. Corpus-wide, 429 -> 447 candidates
#: over 32 -> 33 documents with a plan, and characters in no segment stay 0.
EXPECTED_CONCEPTS = 453
EXPECTED_HITS = 5
#: Rank per question INDEX, `None` for the row that misses on every bundle and
#: every configuration measured so far. The identity is the index; the question
#: stays in the local-only gold set.
EXPECTED_RANKS = (1, 1, 1, 1, 1, None)
@requires_default_bundle
def test_the_default_bundle_holds_its_concept_count() -> None:
assert len(list(okf_consume.enumerate_concepts(DEFAULT_BUNDLE))) == EXPECTED_CONCEPTS
@requires_default_bundle
def test_hit_at_eight_holds_rank_one_on_every_row_it_held() -> None:
"""The acceptance criterion round 6's default move had to clear.
Not the hit COUNT alone: the count survived a configuration that lost a
row from rank 1 to rank 2, which is exactly how the previous round's
regression hid. The rank per row is the pin.
On THIS bundle that is not a hypothetical -- see the test below, which
reproduces the fall on these exact bytes by turning the reading-side
default off.
"""
questions = json.loads(GOLD_SET.read_text(encoding="utf-8"))["questions"]
assert len(questions) == len(EXPECTED_RANKS), "the gold set changed shape"
ranks = []
for entry in questions:
payload = okf_consume.build_payload(DEFAULT_BUNDLE, question=entry["question"])
excerpts = payload["excerpts"]
assert isinstance(excerpts, list)
ranks.append(okf_consume_measure.hit_rank(excerpts, entry["gold_document"]))
assert tuple(ranks) == EXPECTED_RANKS, f"hit@8 ranks moved: {ranks}"
assert sum(rank is not None for rank in ranks) == EXPECTED_HITS
@requires_default_bundle
def test_the_bundle_declares_the_identity_the_reader_needs() -> None:
"""Whatever else moves, the bundle stays one the reading direction opens."""
assert okf_consume.root_bundle_id_of(DEFAULT_BUNDLE) == "k2-trinn1-20260903"
@requires_default_bundle
def test_the_reading_default_is_what_holds_row_one_on_these_bytes() -> None:
"""The known-negative, on the shipped bundle rather than a fixture.
Round 7 moved `--sheet-section-rows --keep-table-heading` into the build
default, which splits row 1's gold document from 1 concept into 12. Round 6
measured that exact split costing row 1 its rank, and held the two rules
back for it. What removed the cost is `consume.DEFAULT_TIE_SHARED_RANK`,
and this test is the proof that it is still what removes it: turn it off
on these bytes and the fall comes back.
Without this, `EXPECTED_RANKS` above would be a green assertion with no
stated cause, and a later change to the fusion could take the cause away
while the pin stayed green on some other accident.
"""
questions = json.loads(GOLD_SET.read_text(encoding="utf-8"))["questions"]
ranks = []
for entry in questions:
payload = okf_consume.build_payload(
DEFAULT_BUNDLE, question=entry["question"], tie_shared_rank=False
)
excerpts = payload["excerpts"]
assert isinstance(excerpts, list)
ranks.append(okf_consume_measure.hit_rank(excerpts, entry["gold_document"]))
assert ranks[0] == 2, "the known-negative stopped being negative"
assert tuple(ranks[1:]) == EXPECTED_RANKS[1:]
@requires_default_bundle
def test_the_stem_rule_holds_every_rank_on_the_shipped_bytes() -> None:
"""Round 10's reading-side default, pinned the way `--tie-shared-rank` is.
The rule requires a shared prefix to be a word the bundle uses. It moves a
payload with NO bundle changing, which is the same class of change as the
tie-break above, so it gets the same treatment: the ranks are pinned WITH
it, and the test below pins what it costs when it is off.
Measured on these bytes, control run first: `under` occurs 79 times by
equality and matches 172 concepts by prefix; `bilateral` occurs 0 times and
matched 400 of 453 before this rule and 0 after; `standhaftig` 0 and
219 -> 56. `undersjoisk` goes 172 -> 162 and stops there, because `under`
IS a word here -- a genuine Norwegian morpheme rather than a residual.
"""
questions = json.loads(GOLD_SET.read_text(encoding="utf-8"))["questions"]
ranks = []
for entry in questions:
payload = okf_consume.build_payload(DEFAULT_BUNDLE, question=entry["question"])
excerpts = payload["excerpts"]
assert isinstance(excerpts, list)
ranks.append(okf_consume_measure.hit_rank(excerpts, entry["gold_document"]))
assert tuple(ranks) == EXPECTED_RANKS
@requires_default_bundle
def test_the_stem_rule_is_what_keeps_bilateral_out_of_four_hundred_concepts() -> None:
"""The known-negative for the stem rule, on the shipped bundle.
Without it the pin above would be green with no stated cause. The number
that names the cause is the one the rule was built for: a word occurring
ZERO times reaching 400 of 453 concepts.
The control is measured FIRST and in the same loop, because round 9's first
attempt at this measurement returned 0 for the probe AND for the control --
`enumerate_concepts` yields `str`, not `Path` -- and a zero that is really a
broken query is indistinguishable from a zero that is a result.
"""
root_id = okf_consume.root_bundle_id_of(DEFAULT_BUNDLE)
concepts = [
okf_consume.read_concept(
DEFAULT_BUNDLE / f"{concept_id}.md",
bundle_root=DEFAULT_BUNDLE,
root_bundle_id=root_id,
)
for concept_id in okf_consume.enumerate_concepts(DEFAULT_BUNDLE)
]
documents = [okf_consume.normalise(text) for text in okf_consume.searchable_text(concepts)]
stems = frozenset(token for document in documents for token in document)
def reach(word: str, *, stemmed: bool) -> int:
token = okf_consume.normalise(word)[0]
return sum(
1
for document in documents
for _ in (0,)
if any(
okf_consume.tokens_match(token, other, stems=stems if stemmed else None)
for other in document
)
)
assert sum(1 for d in documents if okf_consume.normalise("under")[0] in d) == 79, (
"the CONTROL first: `under` really does occur, so the query can find"
)
assert reach("bilateral", stemmed=False) == 400, "the defect, on these bytes"
assert reach("bilateral", stemmed=True) == 0, "and what the rule does to it"
assert reach("under", stemmed=True) == 172, "a real word keeps its compounds"