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:
Kjell Tore Guttormsen 2026-09-09 00:25:51 +02:00
commit b01492b7f5
10 changed files with 743 additions and 7 deletions

View file

@ -70,6 +70,7 @@ from __future__ import annotations
import argparse
import sys
import tempfile
from functools import partial
from pathlib import Path
from .corpus import LOG_NAME, CorpusReport, load_plans, measure
@ -77,6 +78,7 @@ from .errors import IngestError
from .inbox import walk_inbox
from .profiles import SEGMENTED_OKF_V0_2, STRUCTURED_V1, BundleProfile
from .propose import ProposerError
from .propose import heading_reserve_applies
from .propose import run as propose_run
__all__ = ["DEFAULT_STAMP", "build", "main", "measure"]
@ -150,6 +152,18 @@ DEFAULT_UNIT_FOLD = True
DEFAULT_PDF_HEADINGS = False
DEFAULT_OCR = False
#: Round 5's third value on the SAME axis, and off for a third reason: not
#: caution, not construction, but a reachable set with no reference in it.
#: The reserve reads typography only where the outline gate admits no run, so
#: over the 43-document corpus it can act on 4 of 39 readable documents -- and
#: on the twelve-position reference it changes NOTHING, because the only
#: positions where it fires are one PDF whose glyphs carry no ToUnicode
#: mapping and four office documents the PDF reader never touches. The
#: position it was built for numbers its own chapters, so the reserve is
#: silent there by construction. Measured in
#: `docs/2026-09-08-k3-runde5-hitat8-og-skriftakse.md`.
DEFAULT_PDF_HEADINGS_RESERVE = False
#: The timestamp written when the caller passes none, for the ingest stamp and
#: the proposal stamp alike. ONE constant: two independently-defaulted literals
#: drift, and the drift shows up only as two bundles differing in a field
@ -171,6 +185,7 @@ def _propose_plans(
sheet_section_rows: bool = False,
drop_wrapped_outline: bool = False,
pdf_headings: bool = False,
pdf_headings_reserve: bool = False,
ocr: bool = False,
) -> tuple[int, int, int]:
"""Propose a plan per dropped file. Returns (written, nothing, failed).
@ -203,6 +218,7 @@ def _propose_plans(
sheet_section_rows=sheet_section_rows,
drop_wrapped_outline=drop_wrapped_outline,
pdf_headings=pdf_headings,
pdf_headings_reserve=pdf_headings_reserve,
ocr=ocr,
)
except ProposerError as exc:
@ -234,6 +250,7 @@ def build(
sheet_section_rows: bool = False,
drop_wrapped_outline: bool = False,
pdf_headings: bool = DEFAULT_PDF_HEADINGS,
pdf_headings_reserve: bool = DEFAULT_PDF_HEADINGS_RESERVE,
ocr: bool = DEFAULT_OCR,
) -> CorpusReport:
"""Folder in, bundle out. The whole command, minus argument parsing.
@ -252,6 +269,13 @@ def build(
"""
if proposed_at is None:
proposed_at = ingested_at
# Bound to THIS run's outline minimum, once, so the proposer and the door
# cannot be handed two different thresholds for the same question.
reserve = (
partial(heading_reserve_applies, outline_run=outline_run)
if pdf_headings_reserve and not pdf_headings
else None
)
if not segments:
report = measure(
inbox,
@ -259,6 +283,7 @@ def build(
ingested_at=ingested_at,
profile=STRUCTURED_V1,
pdf_headings=pdf_headings,
heading_reserve=reserve,
ocr=ocr,
)
_write_log(bundle, report, profile=STRUCTURED_V1)
@ -292,6 +317,7 @@ def build(
sheet_section_rows=sheet_section_rows,
drop_wrapped_outline=drop_wrapped_outline,
pdf_headings=pdf_headings,
pdf_headings_reserve=pdf_headings_reserve,
ocr=ocr,
)
print(
@ -308,6 +334,7 @@ def build(
profile=SEGMENTED_OKF_V0_2,
root_frontmatter_values={"okf_version": okf_version, "bundle_id": bundle_id},
pdf_headings=pdf_headings,
heading_reserve=reserve,
ocr=ocr,
)
_write_log(bundle, report, profile=SEGMENTED_OKF_V0_2)
@ -507,8 +534,14 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
)
build_parser.add_argument(
"--pdf-headings",
choices=("none", "font"),
default="font" if DEFAULT_PDF_HEADINGS else "none",
choices=("none", "font", "font-reserve"),
default=(
"font"
if DEFAULT_PDF_HEADINGS
else "font-reserve"
if DEFAULT_PDF_HEADINGS_RESERVE
else "none"
),
help=(
"how a PDF's headings are recovered before any arm reads the text. "
"none (the default) is today's extraction: a PDF carries no heading "
@ -518,7 +551,11 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
"heading in the same markdown the office path produces, so the "
"existing heading rule reads it and no PDF-only grammar exists. A "
"CONJUNCTION, measured: adding weight as a disjunct took precision "
"from 0.786 to 0.524"
"from 0.786 to 0.524. font-reserve is font applied ONLY to a "
"document whose own numbering the outline arm finds nothing of -- "
"typography as a second source where there is no first one, never "
"on top of one. Three values of one option, so no caller can ask "
"for two at once"
),
)
build_parser.add_argument(
@ -566,6 +603,7 @@ def main(argv: list[str] | None = None) -> int:
sheet_section_rows=args.sheet_section_rows,
drop_wrapped_outline=args.drop_wrapped_outline,
pdf_headings=args.pdf_headings == "font",
pdf_headings_reserve=args.pdf_headings == "font-reserve",
ocr=args.ocr,
)
except (IngestError, OSError, ValueError) as exc: