feat(propose,cli): typography as a reserve, and the two of our own numbers it took to measure it
K3 round 5. Three questions, three answers, and two of them correct a figure this repository published. RETRIEVAL FIRST, because it could have reversed a default. hit@8 over the six questions on BOTH K2 bundles -- Arm B at 629 concepts and the shipped default at 492 -- is 5 of 6 with ranks 1,1,1,1,1,- on each, so 0 of 6 rows lost. The order's rule reverses `--unit-fold` at >= 2 of 6; it does not fire, and the default stands. The gold sets shrink (49->26, 20->17, 43->36, 11->18) while every rank holds at 1, which is the fold merging concepts rather than removing a document from the top. TWO PUBLISHED NUMBERS CORRECTED, both ours. The S7 candidate ranks 96 of 629 and 159 of 492 were measured with the cost vocabulary passed to `concept_scores` and NOT to `document_scores`, while `build_payload` passes it to both; scored the way the shipped payload scores it, the same concept is 10 of 629 and 19 of 492. And round 4 attributed its non-delivery to the default move -- measured here, it is not delivered on the Arm B bundle either, for a different reason (knapsack eviction at 68 046 bytes of a 120 000 budget, versus `below_k`). That column had been inherited from round 3's own build, never re-measured. `--pdf-headings font-reserve`, OFF, and the hypothesis behind it is falsified by its own condition rather than by a score: position 7, the one position the flag exists for, has THREE outline runs, so the reserve is silent there at every minimum. It changes 0 of 12 cells on the reference and reaches 4 of 39 corpus documents, none of them rated. Built anyway because it was authorised and because the condition is now measured rather than assumed. The predicate lives in one place (`propose.heading_reserve_applies`) and the door receives it as a callable, like `gate`: a plan indexes the exact string it was proposed against, so a reserve firing on one side only would make every document it touches a coded rejection. The `xlsx` re-reading is confirmed on the artifact -- 11 `rule:sheet-section` units plus 1 `rule:table-block` ingress -- but the number alone makes the cell worse (distance 1 -> 2), because the criterion counts that ingress as a table that should have been merged. A hit needs both halves ratified, and the reference is the operator's. `--sheet-section-rows` as a default: three cells better and none worse on the twelve positions, but the K2 control moves -- row 1's gold document splits 1 -> 12 concepts and its best concept ranks 2 instead of 1. Condition not met, default not moved. Default build byte-identical before and after (`diff -r`, 30 md files). Suite 1441 -> 1449; three of the eight were red first. Report: docs/2026-09-08-k3-runde5-hitat8-og-skriftakse.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
53d5c74c96
commit
b01492b7f5
10 changed files with 743 additions and 7 deletions
20
tests/fixtures/make_fixtures.py
vendored
20
tests/fixtures/make_fixtures.py
vendored
|
|
@ -65,6 +65,23 @@ FONT_HEADING_CONTENT = (
|
|||
)
|
||||
|
||||
|
||||
# The SAME typography as `FONT_HEADING_CONTENT`, over a document that numbers
|
||||
# its own chapters. It exists for the heading RESERVE and for nothing else: the
|
||||
# reserve reads typography only where Arm D's outline gate admits no run, so
|
||||
# proving it stays silent needs a document where both signals are present and
|
||||
# only one of them may be used. Three ascending integers at line start, which
|
||||
# is a run at the build default's minimum of 3.
|
||||
NUMBERED_FONT_CONTENT = (
|
||||
b"BT /F2 20 Tf 50 700 Td (Forord) Tj ET\n"
|
||||
b"BT /F1 10 Tf 50 670 Td (1 Generelle krav) Tj ET\n"
|
||||
b"BT /F1 10 Tf 50 640 Td (Utkilingen skal ha helning 1:15.) Tj ET\n"
|
||||
b"BT /F1 10 Tf 50 610 Td (2 Merking) Tj ET\n"
|
||||
b"BT /F1 10 Tf 50 580 Td (Kravet gjelder alle veiklasser.) Tj ET\n"
|
||||
b"BT /F1 10 Tf 50 550 Td (3 Vedlegg) Tj ET\n"
|
||||
b"BT /F1 10 Tf 50 520 Td (Vedlegget er eget oppslag.) Tj ET\n"
|
||||
)
|
||||
|
||||
|
||||
def build_two_font_pdf(content: bytes) -> bytes:
|
||||
"""A one-page PDF whose resources declare BOTH a regular and a bold font.
|
||||
|
||||
|
|
@ -419,6 +436,9 @@ if __name__ == "__main__":
|
|||
(HERE / "font-heading-krav.pdf").write_bytes(build_two_font_pdf(FONT_HEADING_CONTENT))
|
||||
print("wrote font-heading-krav.pdf")
|
||||
|
||||
(HERE / "numbered-font-krav.pdf").write_bytes(build_two_font_pdf(NUMBERED_FONT_CONTENT))
|
||||
print("wrote numbered-font-krav.pdf")
|
||||
|
||||
for name, parts in (
|
||||
("two-line-krav.docx", _DOCX_PARTS),
|
||||
("no-styles-krav.docx", _DOCX_NO_STYLES_PARTS),
|
||||
|
|
|
|||
42
tests/fixtures/numbered-font-krav.pdf
vendored
Normal file
42
tests/fixtures/numbered-font-krav.pdf
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
%PDF-1.4
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 4 0 R /Resources << /Font << /F1 5 0 R /F2 6 0 R >> >> >>
|
||||
endobj
|
||||
4 0 obj
|
||||
<< /Length 353 >>
|
||||
stream
|
||||
BT /F2 20 Tf 50 700 Td (Forord) Tj ET
|
||||
BT /F1 10 Tf 50 670 Td (1 Generelle krav) Tj ET
|
||||
BT /F1 10 Tf 50 640 Td (Utkilingen skal ha helning 1:15.) Tj ET
|
||||
BT /F1 10 Tf 50 610 Td (2 Merking) Tj ET
|
||||
BT /F1 10 Tf 50 580 Td (Kravet gjelder alle veiklasser.) Tj ET
|
||||
BT /F1 10 Tf 50 550 Td (3 Vedlegg) Tj ET
|
||||
BT /F1 10 Tf 50 520 Td (Vedlegget er eget oppslag.) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>
|
||||
endobj
|
||||
6 0 obj
|
||||
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>
|
||||
endobj
|
||||
xref
|
||||
0 7
|
||||
0000000000 65535 f
|
||||
0000000009 00000 n
|
||||
0000000058 00000 n
|
||||
0000000115 00000 n
|
||||
0000000251 00000 n
|
||||
0000000654 00000 n
|
||||
0000000751 00000 n
|
||||
trailer
|
||||
<< /Size 7 /Root 1 0 R >>
|
||||
startxref
|
||||
853
|
||||
%%EOF
|
||||
173
tests/test_pdf_heading_reserve.py
Normal file
173
tests/test_pdf_heading_reserve.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
"""The font reader as a RESERVE: typography only where the outline gate is empty.
|
||||
|
||||
Round 4 measured the font reader two ways and shipped neither as a default. On
|
||||
TOP of Arm D it took `pdf` from 2 of 8 to 0 of 8, because the documents it broke
|
||||
already numbered their own chapters and the second heading source could only
|
||||
ADD a title page and a contents listing. INSTEAD of Arm D it scored 1 of 8 but
|
||||
landed one position exactly on its reference count, which is the observation
|
||||
this flag comes from: use typography only where the document does not number
|
||||
itself.
|
||||
|
||||
The mechanism is one condition, and these tests pin the condition rather than
|
||||
the outcome: the reserve reads a rendering it may not have asked for only when
|
||||
`outline_runs` admits nothing at the configured minimum. Everything else --
|
||||
which arm ships, whether the reserve helps -- is a measurement, and the
|
||||
measurement is in `docs/2026-09-08-k3-runde5-hitat8-og-skriftakse.md`.
|
||||
|
||||
**The reserve is OFF by default and the measurement says it must stay off**: on
|
||||
the twelve-position reference it changes not one cell, because the only
|
||||
positions where it fires are a PDF whose glyphs carry no ToUnicode mapping and
|
||||
four office documents the PDF reader never touches.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
|
||||
from llm_ingestion_okf import cli
|
||||
from llm_ingestion_okf.extract import extract_text
|
||||
from llm_ingestion_okf.propose import build_plan, heading_reserve_applies
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures"
|
||||
NUMBERED_PDF = FIXTURES / "numbered-font-krav.pdf"
|
||||
FONT_PDF = FIXTURES / "font-heading-krav.pdf"
|
||||
|
||||
|
||||
def _extract(path: Path, **kwargs: object) -> str:
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
return extract_text(path.name, path.read_bytes(), **kwargs) # type: ignore[arg-type]
|
||||
|
||||
|
||||
# --- the condition ----------------------------------------------------------
|
||||
|
||||
|
||||
def test_the_reserve_is_silent_where_the_document_numbers_itself() -> None:
|
||||
"""The whole point: a document with its own chapter run keeps its own run."""
|
||||
assert heading_reserve_applies(_extract(NUMBERED_PDF), outline_run=3) is False
|
||||
|
||||
|
||||
def test_the_reserve_fires_where_the_outline_gate_admits_nothing() -> None:
|
||||
assert heading_reserve_applies(_extract(FONT_PDF), outline_run=3) is True
|
||||
|
||||
|
||||
def test_a_run_shorter_than_the_minimum_is_not_a_run() -> None:
|
||||
"""The condition reads the gate as CONFIGURED, not a fixed grammar.
|
||||
|
||||
The same three-chapter document is a run at 3 and not one at 4, so raising
|
||||
the arm's minimum widens the reserve rather than leaving it behind.
|
||||
"""
|
||||
text = _extract(NUMBERED_PDF)
|
||||
assert heading_reserve_applies(text, outline_run=4) is True
|
||||
|
||||
|
||||
def test_with_arm_d_off_the_reserve_fires_everywhere() -> None:
|
||||
"""`--outline-run 0` admits no run at all, so the reserve is unconditional.
|
||||
|
||||
Stated rather than left to the reader: that configuration IS round 4's
|
||||
"font instead of Arm D", which measured 1 of 8. A caller combining the two
|
||||
flags gets that, and gets it deliberately.
|
||||
"""
|
||||
assert heading_reserve_applies(_extract(NUMBERED_PDF), outline_run=0) is True
|
||||
|
||||
|
||||
# --- the effect on a plan ---------------------------------------------------
|
||||
|
||||
|
||||
def _entries(text: str) -> list[str]:
|
||||
plan = build_plan(
|
||||
Path("x.pdf"),
|
||||
text,
|
||||
b"",
|
||||
okf_type="reference",
|
||||
proposed_at="1970-01-01T00:00:00Z",
|
||||
outline_run=3,
|
||||
table_grid=True,
|
||||
unit_fold=True,
|
||||
)
|
||||
return [str(entry["title"]) for entry in plan["entries"]]
|
||||
|
||||
|
||||
def test_the_numbered_document_is_segmented_by_its_own_numbering() -> None:
|
||||
"""The reserve buys nothing here, and that is the guarantee, not a defect.
|
||||
|
||||
Measured rather than assumed, and the measurement is stronger than the
|
||||
guarantee: at the build default this fixture lands on the same three units
|
||||
from BOTH renderings, because the outline arm's run wins the document and
|
||||
the fold puts the font-inferred `Forord` inside the first unit rather than
|
||||
beside it. So the reserve's silence here has two independent reasons, and
|
||||
only one of them -- the condition -- is what this flag controls. The
|
||||
condition itself is pinned above, on the extraction, where it is decided.
|
||||
"""
|
||||
assert _entries(_extract(NUMBERED_PDF)) == ["Generelle krav", "Merking", "Vedlegg"]
|
||||
assert _entries(_extract(NUMBERED_PDF, pdf_headings=True)) == [
|
||||
"Generelle krav",
|
||||
"Merking",
|
||||
"Vedlegg",
|
||||
]
|
||||
|
||||
|
||||
# --- the flag ---------------------------------------------------------------
|
||||
|
||||
|
||||
def test_the_build_default_does_not_reach_the_reserve() -> None:
|
||||
"""Every byte-pinned golden depends on this, so it is a test and not a note."""
|
||||
assert cli.DEFAULT_PDF_HEADINGS is False
|
||||
assert cli.DEFAULT_PDF_HEADINGS_RESERVE is False
|
||||
|
||||
|
||||
def test_the_reserve_is_reachable_from_the_build_command(tmp_path: Path) -> None:
|
||||
"""`--pdf-headings font-reserve` is a third value on the same axis.
|
||||
|
||||
One axis, three values, because `none`, `font` and `font-reserve` are three
|
||||
answers to one question -- how a PDF's headings are recovered -- and a
|
||||
separate boolean flag would let a caller ask for two of them at once.
|
||||
"""
|
||||
base = [
|
||||
"build",
|
||||
str(tmp_path),
|
||||
"--bundle",
|
||||
str(tmp_path / "b"),
|
||||
"--bundle-id",
|
||||
"x",
|
||||
"--okf-version",
|
||||
"0.2",
|
||||
]
|
||||
assert cli.parse_args([*base, "--pdf-headings", "font-reserve"]).pdf_headings == "font-reserve"
|
||||
assert cli.parse_args([*base, "--pdf-headings", "font"]).pdf_headings == "font"
|
||||
assert cli.parse_args(base).pdf_headings == "none"
|
||||
|
||||
|
||||
def test_the_reserve_reaches_the_proposer_and_the_door_alike(tmp_path: Path) -> None:
|
||||
"""Propose and apply must choose the SAME rendering, or nothing applies.
|
||||
|
||||
A plan indexes the exact string it was proposed against (`text_sha256`), so
|
||||
a reserve that fired on one side and not the other would turn every
|
||||
document it touches into a coded rejection. The build is the test: the
|
||||
unnumbered PDF must come back segmented by its typography, and the numbered
|
||||
one by its own chapters, in ONE run with ONE flag.
|
||||
"""
|
||||
inbox = tmp_path / "in"
|
||||
inbox.mkdir()
|
||||
(inbox / "numbered.pdf").write_bytes(NUMBERED_PDF.read_bytes())
|
||||
(inbox / "unnumbered.pdf").write_bytes(FONT_PDF.read_bytes())
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
report = cli.build(
|
||||
inbox,
|
||||
tmp_path / "bundle",
|
||||
bundle_id="reserve-fixture",
|
||||
okf_version="0.2",
|
||||
pdf_headings_reserve=True,
|
||||
)
|
||||
assert report.rejected == 0
|
||||
names = sorted(path.name for path in (tmp_path / "bundle").rglob("*.md"))
|
||||
# The unnumbered PDF: the default leaves it one flat concept (round 4 pins
|
||||
# that), and the reserve gives it the unit its typography names.
|
||||
assert "generelle-tekniske-krav.md" in names, names
|
||||
# The numbered PDF: its own chapters, and no `Forord` the reserve added.
|
||||
assert "generelle-krav.md" in names, names
|
||||
assert "merking.md" in names, names
|
||||
assert "vedlegg.md" in names, names
|
||||
assert "forord.md" not in names, names
|
||||
Loading…
Add table
Add a link
Reference in a new issue