feat(quality): okf quality, a per-file-type verdict with the denominator

G37. `okf check` is a CONTRACT check and a green one is not a quality gate:
measured 2026-09-10 by `vegnormal-okf`, three arms over one corpus all
returned 0 findings and exit 0 while their hit@k ranged from 6 of 6 to 0 of 6.
`okf quality <bundle>` asks the other question, per file type, with the
denominator on every line. A separate command rather than `okf check
--quality`, because the two answer different questions and a caller must not
be able to read one as the other. `okf check` is untouched.

Three verdicts and no fourth -- PASS, FAIL, UNMEASURED -- and a type with no
measured threshold is never PASS. Exit 0 judged and clean, 1 at least one
FAIL, 2 did not run, 3 nothing could be judged: exit 0 over a table of
unmeasured rows would be the silent pass this command exists to stop.

Two bars today, both `structure_null_share` (documents of a type yielding
exactly one concept), read off the pinned 43-document reference bundle: .pdf
8/32, .docx 2/5. Plus one definitional bar for every type, taken from the
harness's own degenerate-merge definition: 0 concepts with an empty body,
measured 0 of 8 602 concepts over four bundles. A bar needs five documents on
BOTH sides -- its own and the judged bundle's -- so .xlsx (2), .xml (1) and
every type with no corpus class in `extract._EVIDENCE` are UNMEASURED and
print their numbers without a verdict.

The floor on the judged bundle was found by RUNNING the gate, not by reading
it: one PDF cut into 2 182 concepts scored 0 of 1 against the 32-document
reference and read as PASS.

The gate walks the index tree and never a directory (SS 9.2; controlled
against the listing on four bundles, 453 / 2 761 / 3 206 / 446 either way),
and prints the bundle's own run log beside its counts -- a document rejected
at extraction leaves no row in the bundle, so the pinned corpus's 33 PDFs
show up as 32 and the two denominators must never be read as one.

Three of the order's five premises moved when re-measured, and they are in the
document rather than glossed: the four evidence corpora carry `source_file` on
0 of 446, 0 of 1 133, 0 of 270 and 0 of 2 756 concepts, so they name no file
type and cannot PASS; "41,6 %" is `vegnormal-okf`'s number and not in this
repository; and the same 828-document bundle carries two published hit@k
figures from two question sets.

Three candidate metrics measured and NOT shipped: duplicate titles within a
document (0 of 3 206 on the known-bad arm against 349 of 2 761 on the
known-good one) and short concepts (5.6 % against 14.6 %) order the two arms
the wrong way round; duplicate titles across the whole bundle order all four
correctly (37.8 / 16.3 / 12.6 / 5.7 %) and still ship without a bar, because
any bar separating them is read off the two bundles it would judge.

19 new tests, each rule exercised in both directions; the three README pins
were each driven red before being kept. Suite 1 850 passed, 1 skipped, 1 851
collected, run after `git add` -- +19 against a base of 1 832 collected,
measured on the stashed tree (STATE's 1 831 is one short of that).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-13 00:13:00 +02:00
commit 6ea8fcd3c7
8 changed files with 970 additions and 1 deletions

View file

@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **`okf quality <bundle>` -- a per-file-type verdict, with the denominator
(G37).** `okf check` is a CONTRACT check, and a green one is not a quality
gate: measured 2026-09-10 by `vegnormal-okf`, three arms over one corpus all
returned 0 findings and exit 0 while their hit@k ranged from 6 of 6 to 0 of 6.
The new command asks the other question. Three verdicts and no fourth --
`PASS`, `FAIL`, `UNMEASURED` -- and a type with no measured threshold is never
`PASS`; exit **0** judged and clean, **1** at least one `FAIL`, **2** did not
run, **3** nothing could be judged, because exit 0 over a table of unmeasured
rows would be the silent pass the command exists to stop. Two thresholds
today, both `structure_null_share` (the share of a type's documents that
yielded exactly one concept), read off the pinned 43-document reference
bundle: `.pdf` 8/32 and `.docx` 2/5, plus one definitional bar that applies to
every type (0 concepts with an empty body, measured 0 of 8 602 over four
bundles). A bar needs at least five documents on BOTH sides -- its own and the
judged bundle's -- so `.xlsx` (2), `.xml` (1) and every type with no corpus
class in `extract._EVIDENCE` are `UNMEASURED` and print their numbers without
a verdict. The gate reads the index tree, never a directory (SS 9.2), and
prints the bundle's own run log beside its counts, because a document rejected
at extraction leaves no row in the bundle at all -- the pinned corpus holds 33
PDFs and the bundle shows 32. `okf check` is untouched.
`docs/2026-09-12-g37-terskler.md` carries the table, the nine bundles behind
it, the order's own premises re-measured (three of five moved), and three
candidate metrics measured and NOT shipped -- two of them ordering a known-bad
arm and a known-good one the wrong way round. The README publishes the bars
behind a `<!-- quality-thresholds: ... -->` marker that
`tests/test_docs_promises.py` pins to the code and to the document.
### Changed
- **The README states every file type the extractor registry reads (K3-26).**

View file

@ -755,6 +755,39 @@ and fixtures, never code.
move (it is not delivered on the Arm B bundle either, by a different
mechanism). hit@8 over the six published questions holds at **5 of 6 on both
K2 bundles**, so the default move cost the retrieval side nothing.
- **Judge a bundle: `okf quality <bundle>` (G37, 2026-09-12).** A per-file-type
verdict, with the denominator on every line, and it is a SEPARATE command
from `okf check` on purpose: `check` is the contract check, and a green one
is not a quality gate -- measured 10.09 by `vegnormal-okf`, three arms over
one corpus all gave 0 findings and exit 0 while hit@k ranged 6 of 6 to 0 of
6. Three verdicts and no fourth (`PASS` / `FAIL` / `UNMEASURED`), a type with
no measured threshold is **never** `PASS`, and exit **3** exists for
"nothing could be judged" so exit 0 over a table of unmeasured rows cannot
be a silent pass (0 clean, 1 a `FAIL`, 2 did not run). Two bars today, both
`structure_null_share` off the pinned 43-document reference -- `.pdf` 8/32,
`.docx` 2/5 -- plus one definitional bar for every type (0 empty bodies,
measured **0 of 8 602** concepts over four bundles). A bar needs **five
documents on BOTH sides**, its own and the judged bundle's, which was found
by RUNNING the gate: a one-PDF bundle scored 0 of 1 against the 32-document
reference and read as `PASS`. **The bars are regression bars against a
pinned artifact, never a quality claim**, and the defect that started G37 --
the HTML arm's 1 148 of 2 761 boundaries -- is `UNMEASURED` here, because no
bundle-only metric reaches it: three candidates were measured over the same
four bundles and **two order the known-bad and known-good arms the wrong way
round** (duplicate titles within a document 0/3 206 against 349/2 761; short
concepts 5.6 % against 14.6 %), while the third (duplicate titles across the
whole bundle, 37.8 / 16.3 / 12.6 / 5.7 %) orders them correctly and ships
anyway WITHOUT a bar, since any bar separating them sits between the two
bundles that define it. **The gate's denominator is the bundle's, never the
corpus's**: a rejected document leaves no row at all (the pinned corpus holds
33 PDFs, the bundle shows 32), so the run log is printed beside the counts
and a bundle without one says so. Three of the four evidence corpora
(`n100`/`n200`/`n500`) carry `source_file` on **0 of 446 / 0 of 1 133 / 0 of
270** concepts, so they name no file type and every row is `UNMEASURED` --
the order expected them to PASS. Thresholds, the nine bundles and the
premises re-measured: `docs/2026-09-12-g37-terskler.md`. README publishes the
bars behind `<!-- quality-thresholds: ... -->`, pinned to the code AND the
document by `tests/test_docs_promises.py`.
- Consume a bundle: `okf consume <bundle> --question "<q>"
[--k N] [--limit N] [--out PATH] [--ref IDENTITY]` — the **pre-pass**
`docs/consumption-contract.md` § 1 defines, and the only reading direction

View file

@ -641,6 +641,50 @@ on a 629-concept bundle against a chance baseline of **1.35 of 6**, with one
control that failed — is `docs/2026-09-07-okf-konsumskill-maaling.md`; the copy
filled by hand for that corpus cannot ship and is no longer the one here.
## Judge a bundle: `okf quality`
`okf check` is a **contract** check — it asks whether a payload carries what a
claim must rest on. It is not a quality gate, and that is measured rather than
conceded: on 2026-09-10 three arms over one corpus all returned 0 findings and
exit 0 while their hit@k ranged from 6 of 6 to 0 of 6.
`okf quality <bundle>` asks the other question, **per file type and with the
denominator on every line**:
```sh
okf quality .okf/my-bundle
```
Three verdicts and no fourth — `PASS`, `FAIL`, `UNMEASURED` — and a type with
no measured threshold is **never** `PASS`. Exit codes: **0** judged and clean,
**1** at least one `FAIL`, **2** the run did not happen, **3** nothing could be
judged (every row `UNMEASURED`), because exit 0 over a table of unmeasured rows
would be the silent pass this command exists to stop.
Two thresholds exist today, both `structure_null_share` — the share of a type's
documents that yielded exactly one concept — read off the pinned 43-document
reference bundle:
<!-- quality-thresholds: .pdf=8/32, .docx=2/5 -->
| file type | threshold | N |
|---|---|---|
| `.pdf` | 8/32 | 32 documents |
| `.docx` | 2/5 | 5 documents |
| every type | 0 concepts with an empty body | definitional |
Every other type is `UNMEASURED`, including `.xlsx` (2 documents), `.xml`
(1 document) and `.html` (no bundle measured here). A threshold needs at least
five documents on both sides — the bundle's and its own — because a `1/1` is
not a rate.
**What a `PASS` is not.** It is a regression bar against a pinned artifact, not
a claim that the cut found the document's own structure. Boundary recall and
hit@k need a fasit and are outside a bundle-only gate; the measurement that
says so, the corpora behind each number, and three candidate metrics that were
measured and not shipped are in
[`docs/2026-09-12-g37-terskler.md`](docs/2026-09-12-g37-terskler.md).
## Consume in Claude Code
A folder of documents to an answer a model can cite, in **three lines**. You do

View file

@ -0,0 +1,181 @@
# G37 — the thresholds, per file type, before the gate
Order `20260912T202210Z-7596115025-from-.claude`, part 1. Base `d310dbb`
(v0.8.5). Measured 2026-09-12 evening local time; the session crossed midnight,
so the commit lands 2026-09-13. Python 3.14.0, guard 1.4.0, no model call and
no network anywhere in what follows.
The order's own words for what this part must produce: "tabell filtype ×
metrikk × terskel × N × kilde", metrics restricted to what is already measured
or measurable without a model, and "en filtype uten måling får terskel
`UNMEASURED`, aldri et gjettet tall".
## § 0 — measured / not measured
**Measured here.** Every count in §§ 25: concept and document counts per file
type over nine bundles, one-concept-document shares, empty-body counts,
duplicate-title shares in two forms, short-concept shares, and which of the
four evidence corpora carry a `source_file` key at all. Each is a shell or
`okf quality` command printed beside it.
**Not measured here.** Boundary recall against a fasit (needs a fasit; § 6),
hit@k (needs a question set and a fasit; § 6), anything about `.txt`, `.csv`,
`.json`, `.htm`, `.pptx`, `.odt`, `.rtf` beyond the fixtures already in the
tree, and whether any threshold below is the *right* bar — ratification is the
operator's, and this document sets regression bars against a pinned artifact,
which is a different thing (§ 4).
## § 1 — the three metric names, defined here because nothing defines them
The order names "grenseandel", "struktur-null-andel", "tomme konsepter",
`Bundle.skipped` and "hit@k". Two of those five have no definition anywhere in
this repository:
Measured on the base commit rather than on the working tree, because this
document and the module beside it introduce both words:
- `git grep -c "grenseandel\|boundary share" d310dbb -- docs src` → **0 files**
- `git grep -ci "struktur.null\|structure.null" d310dbb -- docs src` → **0 files**
- known-positive control for the query form:
`git grep -c "hit@8" d310dbb -- docs`**19 files**, so it can find.
So the definitions below are **mine**, written here so the gate and this
document mean the same thing by them:
| name | definition | what it needs |
|---|---|---|
| `boundary_share` | recovered boundaries ÷ boundaries the source itself declares | a fasit |
| `structure_null_share` | documents of a type yielding exactly ONE concept ÷ documents of that type in the bundle | the bundle alone |
| `empty_concepts` | concepts whose body holds zero non-whitespace characters ÷ concepts of that type | the bundle alone |
| `hit@k` | questions whose gold document reaches the payload at cut `k` ÷ questions | a fasit and a question set |
| run log | `N`, merged and coded rejections, from the bundle's own `log.md` | a bundle Door B wrote |
`Bundle.skipped` does not exist under that name: the door's counts reach a
bundle through `corpus.CorpusReport`, whose written form is the run log above
(`N`, `extracted`, `gated`, `persisted`, `substantive`, `degenerate`,
`rejected`, plus per-code counts). It is a **whole-bundle** figure and carries
no file type, which is the limit in § 5.
## § 2 — the order's own numbers, re-measured first
The coordination note that arrived with the order asked for exactly this, and
three of the five premises moved.
| premise in the order | re-measured | verdict |
|---|---|---|
| 13 extensions in the registry | `_CORE_EXTRACTORS` 7 + `_OPTIONAL_EXTRACTORS` 6 = **13** | holds |
| four evidence corpora under `~/repos/vegnormal-okf/build/ferdig/` | present: `n100-2023`, `n200-2024`, `n500-2024`, `r761-2025` — plus `r761-2025-generisk` and `r761-2025-v080`, two more R761 builds the order does not name | **holds, and is incomplete** |
| "N100 skal gi PASS på de målte typene" | `grep -rl '^source_file:' n100-2023 --include='*.md' | wc -l`**0 of 446 concepts**. Same for `n200-2024` (0 of 1 133), `n500-2024` (0 of 270) and `r761-2025` (0 of 2 756) | **cannot hold**: three of the four name no file type at all, so no type of theirs can be PASS or FAIL |
| "41,6 % grenser" | not in this repository (`grep -rn "41[.,]6" docs/ src/` → 0). It is `vegnormal-okf`'s: `docs/2026-09-10-r2-r761-produkt.md:452`, 1 148 of 2 761 declared STS sections, against the XML arm's 2 759 of 2 761 | **holds, and the owner is another repo** |
| "0 av 6 hit@k" on the HTML arm | two published numbers for the same 828-document bundle: **0 of 6** (vegnormal, their six fasit questions, 10.09) and **2 of 6 → 4 of 6 with `--source-quota 2`** (this repo, `docs/2026-09-10-k3-runde11-html-og-dominans.md:200,217`, questions S1S6) | **holds for one instrument, not for the other** |
The last row is the reason the gate reports a denominator on every line: two
true numbers about one bundle, four apart, because the question set differs.
## § 3 — what each corpus can answer
Command: `okf quality <bundle>` (part 2 of this order), cross-checked against
`grep -rh '^source_file:' <bundle> --include='*.md' | sed 's/.*\.//' | sort |
uniq -c`. The index walk and the directory listing agree on 453, 2 761, 3 206
and 446 concepts — four bundles, both methods.
| bundle | producer | concepts | file types present |
|---|---|---|---|
| `K2-bundle-default-20260912` | Door B (pinned reference) | 453 | `.pdf` 32 docs, `.docx` 5, `.xlsx` 2 |
| `r761-2025-generisk` | Door B | 2 761 | `.xml` 1 doc |
| `sk2-bundle-default` | Door B | 2 182 | `.pdf` 1 doc |
| `860019-mdb-100` | Door B | 3 206 | `.md` 828 docs |
| `n100-2023` | vegnormal's own pipeline | 446 | none — no `source_file` |
| `n200-2024` | vegnormal's own pipeline | 1 133 | none |
| `n500-2024` | vegnormal's own pipeline | 270 | none |
| `r761-2025` | vegnormal's own pipeline | 2 756 | none |
| `b3` | — | — | no `index.md`; the gate refuses to walk a directory (SS 9.2) and exits 2 |
**The four corpora the order names as the evidence base carry no file type.**
The per-file-type thresholds below therefore rest on the K2 reference bundle
and, for context only, on the three single-type bundles.
## § 4 — the table
Threshold semantics, stated before the numbers: a threshold here is a
**regression bar against a pinned artifact**, set at the value measured on that
artifact and never at a rounder number nearby. `PASS` means *no worse than the
reference on this metric*. It is not a claim that the bundle is good, and it is
not the operator's ratified quality bar — the K3 method refuses to set one
inside the measurement that produces it ("setting one inside the same work that
produces the first measurement would be fitting the bar to the number").
A rate needs a denominator. `MIN_DOCUMENTS_FOR_A_THRESHOLD = 5`, and it binds
both the threshold's N and the judged bundle's: this repository's own honesty
limit is "a `1/1` is not a rate" (`docs/2026-09-08-k3-runde2-per-filtype.md`).
| file type | metric | threshold | N | source |
|---|---|---|---|---|
| every type | `empty_concepts` | **0** | 8 602 concepts over 4 Door B bundles, all 0 | definitional: `corpus.CorpusReport` calls a zero-character merge degenerate |
| `.pdf` | `structure_null_share` | **8/32** | 32 documents | `K2-bundle-default-20260912` |
| `.docx` | `structure_null_share` | **2/5** | 5 documents | `K2-bundle-default-20260912` |
| `.xlsx` | — | **UNMEASURED** | 2 documents | below the floor of 5 |
| `.xml` | — | **UNMEASURED** | 1 document | below the floor of 5 |
| `.md` | — | **UNMEASURED** | 828 documents, `structure_null_share` 362/828 measured | the corpus is one generator's cut of one product, and it is the bundle under suspicion; a bar read off it would pass it by construction |
| `.html` | — | **UNMEASURED** | 0 bundles in this repository | the 828-document HTML measurement was made outside it |
| `.htm`, `.txt`, `.csv`, `.json`, `.pptx`, `.odt`, `.rtf` | — | **UNMEASURED** | 0 corpus documents | no corpus class in `extract._EVIDENCE`; `.pptx`/`.odt`/`.rtf` are `constructed`, N = 12 |
### Candidates measured and not shipped
Three fasit-free metrics were measured over the same four Door B bundles, with
the known-bad arm (`860019-mdb-100`, 1 148 of 2 761 boundaries) and the
known-good arm (`r761-2025-generisk`, 2 759 of 2 761) named in advance:
| candidate | known-bad arm | known-good arm | pdf arm | K2 reference | verdict |
|---|---|---|---|---|---|
| duplicate titles within a document | 0/3 206 (0.0 %) | 349/2 761 (12.6 %) | 355/2 182 | 11/453 | **wrong direction** |
| concepts under 40 characters | 181/3 206 (5.6 %) | 402/2 761 (14.6 %) | 1/2 182 | 25/453 | **wrong direction** |
| duplicate titles across the whole bundle | 1 211/3 206 (**37.8 %**) | 349/2 761 (12.6 %) | 355/2 182 (16.3 %) | 26/453 (5.7 %) | orders correctly, **no bar** |
The third orders all four bundles correctly and is still not shipped: any bar
that separates them sits between 16.3 % and 37.8 %, which is a number read off
the two bundles it would judge. That is the operator's call and it is stated in
STATE rather than taken here.
## § 5 — three limits that are properties of the instrument
1. **A rejected document leaves no trace in the bundle.** The pinned K2 bundle
holds 32 `.pdf` documents; the corpus held 33, and one was rejected
`extractor_empty_pdf`. The gate's denominator is the bundle's, never the
corpus's, and it prints the run log (`N = 43, merged = 39, coded rejections
= 4`) beside its own numbers so the two are never read as one. A bundle
without a log says "no run log" rather than reporting zero rejections.
2. **The run log carries no file type.** `codes` is per rejection code, not per
extension, so "this type failed to extract entirely" is invisible to a
bundle-only gate. Naming that would be a change in `corpus.py`, not here.
3. **The reference bundle passes its own bars by construction.** `okf quality`
on `K2-bundle-default-20260912` returning `PASS` on `.pdf` and `.docx` is a
control that the gate reproduces its own reference — nothing more.
## § 6 — what would reach the defect that started this
The 41.6 % arm is `UNMEASURED` under this gate, and that is the honest answer
rather than a hidden one: the metric that fells it is `boundary_share`, which
needs the publisher's own declared structure. R761 has one (2 761 STS `<sec>`
titles, `~/repos/vegnormal-okf/build/sk2-fasit-2761.json`), and 4 of the 9
bundles above have no fasit at all. A `--fasit` input, a threshold on
`boundary_share`, and the operator's bar are one further round, named in STATE
and not begun here.
## Honesty limits
- **Two thresholds, and one of them rests on five documents.** `.docx` at 2/5
is exactly at the floor this document sets for itself. It is a bar against a
pinned artifact, not a rate anyone should quote.
- **`structure_null_share` has never fired on a real bundle.** Of the five Door
B bundles measured, none is worse than its reference; the rule's `FAIL` side
is exercised on constructed bundles in `tests/test_quality.py` only.
- **`empty_concepts` is 0 on 8 602 of 8 602 concepts.** The bar is definitional
and has never fired on real data either. A metric that never fires is not
evidence that nothing is wrong.
- **One rule was found by running the gate, not by reading it.** A one-document
PDF bundle scored 0 of 1 against the 32-document reference and read as
`PASS`. The floor now binds both denominators; the version of this document
written before that run would have published the wrong rule.
- **Nothing here measures answer quality.** hit@k asks whether a gold document
reached the payload; this gate does not even ask that.

View file

@ -96,7 +96,7 @@ __all__ = ["DEFAULT_STAMP", "build", "main", "measure"]
#:
#: Imported lazily inside the dispatch: `okf build` should not pay to import
#: the ranker, and `okf consume` should not pay to import the proposer.
DELEGATED = ("consume", "check", "skill", "project")
DELEGATED = ("consume", "check", "skill", "project", "quality")
def _delegate(command: str, argv: list[str]) -> int:
@ -106,6 +106,8 @@ def _delegate(command: str, argv: list[str]) -> int:
from .contract_check import main as run
elif command == "skill":
from .skill import main as run
elif command == "quality":
from .quality import main as run
else:
from .project import main as run
return run(argv)
@ -592,6 +594,7 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
("check", "check a consumption skill and one payload against the contract"),
("skill", "instantiate the consumption skill template for one bundle"),
("project", "folder in, bundle plus skill out: build and skill in one step"),
("quality", "judge one bundle per file type, with the denominator"),
):
subcommands.add_parser(delegated, help=blurb, add_help=False)
build_parser = subcommands.add_parser(

View file

@ -0,0 +1,407 @@
"""`okf quality` -- a per-file-type verdict on one bundle, with the denominator.
**This is not `okf check`, and the separation is the point.** `okf check` reads
a consumption skill and one payload against `docs/consumption-contract.md`: it
answers whether a payload carries what a claim must rest on. Measured
2026-09-10 by `vegnormal-okf` on three arms over one corpus, it returned 0
findings and exit 0 on all three while their hit@k ranged from 6 of 6 to 0 of 6
-- a green contract check says nothing about whether the cut found anything
worth reading. This module asks that second question, and it is a SEPARATE
command rather than a `--quality` flag on the first for exactly that reason: the
two answer different questions and a caller must not be able to read one as the
other.
Three verdicts and no fourth: `PASS`, `FAIL`, `UNMEASURED`. A type with no
measured threshold is never `PASS` -- an unmeasured row that reads as a passing
one is the failure this gate exists to prevent, and it is the same failure
`extract._EVIDENCE` was built to prevent one layer down.
**What this gate can and cannot see.** Every metric here is computed from the
bundle alone: no fasit, no model call, no clock, no network. That bounds it
sharply, and the bound is measured rather than assumed.
`docs/2026-09-12-g37-terskler.md` SS 4 records three candidates measured over
the same four bundles and what became of each: duplicate titles WITHIN a
document (0 of 3 206 on the known-bad arm against 349 of 2 761 on the known-good
one -- the wrong direction) and the share of very short concepts (5.6 % against
14.6 % -- also the wrong direction) are not shipped; duplicate titles across the
WHOLE bundle order the four bundles correctly (37.8 / 16.3 / 12.6 / 5.7 %) and
are still not shipped, because a bar separating them would have to be placed
between the two bundles that define it, which is fitting the bar to the number.
The defect that started this work -- 1 148 of 2 761 declared boundaries
recovered -- needs a fasit and no bundle-only metric reaches it.
"""
from __future__ import annotations
import argparse
import re
import sys
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
from .consume import ConsumeError, enumerate_concepts, read_concept, root_bundle_id_of
from .corpus import LOG_NAME
from .profiles import SEGMENTED_OKF_V0_2, BundleProfile
CLI_ID = "okf quality"
#: The row a concept lands in when it declares no `source_file`. Not a file
#: type and never treated as one: measured 2026-09-12, three of the four
#: evidence corpora (`n100-2023`, `n200-2024`, `n500-2024`) carry the key on 0
#: of 446, 0 of 1 133 and 0 of 270 concepts, because their producer is not this
#: library's Door B. A per-file-type gate has nothing to say about them, and
#: says that.
NO_SOURCE_FILE = "(no source_file)"
#: A threshold needs a denominator big enough that a single document cannot be
#: the rate. FIVE, and the number is this repository's own honesty limit rather
#: than a statistical claim: `docs/2026-09-08-k3-runde2-per-filtype.md` states
#: "Per file type the denominators are 8, 3 and 1. A `1/1` is not a rate", and
#: `docs/2026-09-04-k3-arm-c.md` says of the three office types with no corpus
#: file at all: "Unmeasured, not passing." Below this floor the row is
#: `UNMEASURED` and its numbers are still printed.
#:
#: It binds BOTH denominators -- the threshold's and the bundle's. Found by
#: running the gate rather than by reading it: one PDF cut into 2 182 concepts
#: scored 0 of 1 against the 32-document reference and read as PASS.
MIN_DOCUMENTS_FOR_A_THRESHOLD = 5
@dataclass(frozen=True)
class Threshold:
"""One measured bar, carrying the measurement it was read off.
The bar is held as the measured PAIR (`limit_null` of `limit_documents`)
rather than a float, so the comparison is exact integer arithmetic and a
bundle sitting exactly at the reference cannot fall to a rounding step.
"""
metric: str
limit_null: int
limit_documents: int
#: Documents behind the measurement. Equal to `limit_documents` today and
#: kept separate because a threshold ratified over a wider corpus than the
#: one it is expressed as would need both numbers.
documents: int
source: str
def exceeded_by(self, null: int, documents: int) -> bool:
"""`null/documents` strictly worse than the reference, without floats."""
return null * self.limit_documents > self.limit_null * documents
def as_share(self) -> str:
return f"{self.limit_null}/{self.limit_documents}"
#: The bars, per extension, and there are two of them. Read off the pinned
#: reference bundle `K2-bundle-default-20260912` (the 43-document corpus
#: `~/corpora/okf-telling-20260829/K2/trinn1`, N = 43, 39 merged) on
#: 2026-09-12, and set at the value measured there rather than at a rounder
#: number nearby: this is a REGRESSION bar against a pinned artifact, not a
#: claim that a bundle at the bar is good. `docs/2026-09-12-g37-terskler.md`
#: carries the table, the corpora and what each number does not prove.
#:
#: Every other type is absent on purpose. `.xlsx` (2 documents) and `.xml`
#: (1 document) are below the floor above; `.html` has no bundle measured in
#: this repository; `.md`, `.txt`, `.csv`, `.json`, `.htm`, `.pptx`, `.odt`
#: and `.rtf` have no corpus class in `extract._EVIDENCE` at all.
THRESHOLDS: dict[str, Threshold] = {
".pdf": Threshold(
metric="structure_null_share",
limit_null=8,
limit_documents=32,
documents=32,
source="K2-bundle-default-20260912 (43-document corpus, 32 pdf documents)",
),
".docx": Threshold(
metric="structure_null_share",
limit_null=2,
limit_documents=5,
documents=5,
source="K2-bundle-default-20260912 (43-document corpus, 5 docx documents)",
),
}
#: The one bar that needs no corpus: a concept whose body holds no
#: non-whitespace character. Taken from the harness's own definition of a
#: degenerate merge (`corpus.CorpusReport.render`: "a merge is degenerate when
#: the extracted text is zero characters after stripping whitespace -- a
#: definition, not a threshold"), so it applies to every type INCLUDING one with
#: no threshold. FAIL is reachable for every row; PASS is not.
EMPTY_BODY_LIMIT = 0
PASS = "PASS"
FAIL = "FAIL"
UNMEASURED = "UNMEASURED"
_LOG_LINE = re.compile(
r"N = (\d+).*?merged = (\d+).*?coded rejections = (\d+)",
re.DOTALL,
)
@dataclass(frozen=True)
class TypeReport:
"""One file type's numbers and its verdict. Every count carries its own N."""
extension: str
documents: int
concepts: int
empty: int
structure_null: int
verdict: str
threshold: Threshold | None
reason: str
def render(self) -> str:
"""One line, and every count on it carries its own denominator.
The `NO_SOURCE_FILE` row prints neither a document count nor a
one-concept share: those concepts all share the same empty
`source_file`, so grouping by it yields `documents 1` for a bundle of
446 -- a number that looks measured and means nothing.
"""
head = f"{self.extension:<18} {self.verdict:<11} "
if self.extension == NO_SOURCE_FILE:
return (
f"{head}concepts {self.concepts:>5} empty {self.empty}/{self.concepts} "
f"-- {self.reason}"
)
share = f"{self.structure_null}/{self.documents}"
bar = f"limit {self.threshold.as_share()}" if self.threshold else "no threshold"
return (
f"{head}documents {self.documents:>5} "
f"concepts {self.concepts:>5} empty {self.empty}/{self.concepts} "
f"one-concept documents {share} ({bar}) -- {self.reason}"
)
@dataclass(frozen=True)
class BundleQuality:
"""One bundle's rows, its run log if it has one, and the exit code they imply."""
bundle_root: Path
bundle_id: str
rows: tuple[TypeReport, ...]
#: The `N`, merged and coded-rejection counts from the bundle's own section
#: 9 log, or `None` when the bundle carries no log. Never defaulted to zero:
#: a rejected document leaves NO concept in the bundle, so without the log
#: the gate cannot know whether a type failed to extract entirely.
run_log: str | None
def row(self, extension: str) -> TypeReport:
for row in self.rows:
if row.extension == extension:
return row
raise KeyError(
f"{extension} is not a row of this bundle: {[r.extension for r in self.rows]}"
)
@property
def exit_code(self) -> int:
"""0 judged and clean, 1 at least one FAIL, 3 nothing could be judged.
`2` is reserved for "did not run" and is returned by `main` alone. The
third code exists because exit 0 over a table of `UNMEASURED` rows would
be exactly the silent pass this gate was built to stop.
"""
if any(row.verdict == FAIL for row in self.rows):
return 1
if any(row.verdict == PASS for row in self.rows):
return 0
return 3
def render(self) -> str:
lines = [
f"# {CLI_ID}: {self.bundle_id}",
"",
f"bundle: {self.bundle_root}",
(
f"run log: {self.run_log}"
if self.run_log is not None
else f"run log: no run log in the bundle ({LOG_NAME} absent) -- the "
"denominators below are the bundle's own, and a document rejected "
"at extraction leaves no row here at all"
),
"",
"## Per file type",
"",
]
lines.extend(row.render() for row in self.rows)
lines.extend(
[
"",
"## What this verdict is not",
"",
"A regression bar against a pinned reference bundle, per file type.",
"PASS means no worse than that reference on the metrics below; it is",
"not a claim that the cut found the document's own structure. Boundary",
"recall and hit@k need a fasit and are outside a bundle-only gate --",
"docs/2026-09-12-g37-terskler.md carries the measurement that says so.",
]
)
return "\n".join(lines) + "\n"
def _extension_of(source_file: str) -> str:
if not source_file.strip():
return NO_SOURCE_FILE
suffix = Path(source_file).suffix.lower()
return suffix if suffix else NO_SOURCE_FILE
def read_run_log(bundle_root: Path) -> str | None:
"""The bundle's own `N`, merged and coded-rejection counts, or `None`."""
log = bundle_root / LOG_NAME
if not log.is_file():
return None
matches = _LOG_LINE.findall(log.read_text(encoding="utf-8"))
if not matches:
return None
total, merged, rejected = matches[-1]
return f"N = {total}, merged = {merged}, coded rejections = {rejected}"
def measure_bundle(
bundle_root: Path, *, profile: BundleProfile = SEGMENTED_OKF_V0_2
) -> BundleQuality:
"""Every concept the index declares, grouped by the extension it came from.
Reached through the index tree and never `rglob`: the index is the bundle's
own statement of what it contains, and `consume.enumerate_concepts` is the
one walker in this library that reads it. Controlled 2026-09-12 against the
directory listing on four bundles -- 453, 2 761, 3 206 and 446 concepts
either way.
"""
root_bundle_id = root_bundle_id_of(bundle_root, profile=profile)
concepts_per_extension: Counter[str] = Counter()
empty_per_extension: Counter[str] = Counter()
documents: dict[str, Counter[str]] = {}
for concept_id in enumerate_concepts(bundle_root, profile=profile):
concept = read_concept(
bundle_root / f"{concept_id}{profile.paths.concept_suffix}",
bundle_root=bundle_root,
root_bundle_id=root_bundle_id,
)
extension = _extension_of(concept.source_file)
concepts_per_extension[extension] += 1
documents.setdefault(extension, Counter())[concept.source_file] += 1
if not "".join(concept.body.split()):
empty_per_extension[extension] += 1
rows = tuple(
_verdict(
extension,
documents=documents[extension],
concepts=concepts_per_extension[extension],
empty=empty_per_extension[extension],
)
for extension in sorted(concepts_per_extension)
)
return BundleQuality(
bundle_root=bundle_root,
bundle_id=root_bundle_id,
rows=rows,
run_log=read_run_log(bundle_root),
)
def _verdict(extension: str, *, documents: Counter[str], concepts: int, empty: int) -> TypeReport:
document_count = len(documents)
structure_null = sum(1 for count in documents.values() if count == 1)
threshold = THRESHOLDS.get(extension)
if extension == NO_SOURCE_FILE and empty <= EMPTY_BODY_LIMIT:
return TypeReport(
extension=extension,
documents=0,
concepts=concepts,
empty=empty,
structure_null=0,
verdict=UNMEASURED,
threshold=None,
reason=(
f"no source_file on {concepts} of {concepts} concepts, so this "
"bundle names no file type at all -- the shape three of the four "
"evidence corpora arrive in, and nothing per file type can be said"
),
)
if empty > EMPTY_BODY_LIMIT:
verdict, reason = (
FAIL,
(
f"{empty} of {concepts} concepts carry no non-whitespace body; the "
"harness calls a zero-character merge degenerate by definition"
),
)
elif threshold is None:
verdict, reason = (
UNMEASURED,
(
"no measured threshold for this type; see "
"docs/2026-09-12-g37-terskler.md, and never read this row as PASS"
),
)
elif document_count < MIN_DOCUMENTS_FOR_A_THRESHOLD:
verdict, reason = (
UNMEASURED,
(
f"{document_count} document(s) of this type in the bundle, below the "
f"floor of {MIN_DOCUMENTS_FOR_A_THRESHOLD}: a share over that few "
"documents is not a rate, whatever the threshold says"
),
)
elif threshold.exceeded_by(structure_null, document_count):
verdict, reason = (
FAIL,
(
f"{structure_null} of {document_count} documents yielded one concept, "
f"worse than the reference {threshold.as_share()} ({threshold.source})"
),
)
else:
verdict, reason = (
PASS,
(f"no worse than the reference {threshold.as_share()} ({threshold.source})"),
)
return TypeReport(
extension=extension,
documents=document_count,
concepts=concepts,
empty=empty,
structure_null=structure_null,
verdict=verdict,
threshold=threshold,
reason=reason,
)
def parse_args(argv: list[str] | None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
prog=CLI_ID,
description=(
"Judge one bundle per file type, with the denominator. Three verdicts: "
"PASS (no worse than the pinned reference), FAIL, and UNMEASURED -- "
"which is never PASS. Exit 0 judged and clean, 1 at least one FAIL, "
"2 did not run, 3 nothing could be judged."
),
)
parser.add_argument("bundle", type=Path, help="the OKF bundle to judge")
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
if not args.bundle.is_dir():
print(f"{CLI_ID}: FAILED - no such bundle: {args.bundle}", file=sys.stderr)
return 2
try:
report = measure_bundle(args.bundle)
except (ConsumeError, OSError, ValueError) as exc:
print(f"{CLI_ID}: FAILED - {exc}", file=sys.stderr)
return 2
print(report.render(), end="")
return report.exit_code
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -207,3 +207,49 @@ def test_the_readme_carries_only_one_file_type_table() -> None:
section = text.split("### Binary extraction", 1)[1].split("\n## ", 1)[0]
rows = [line for line in section.splitlines() if line.strip().startswith("|")]
assert not rows, f"a second file-type table is back under Binary extraction: {rows}"
# The `okf quality` thresholds, in the one place the README writes them. A bar
# published without a test goes false the way the format list did.
_THRESHOLD_LINE = re.compile(r"^<!-- quality-thresholds: (.+) -->$", re.MULTILINE)
THRESHOLD_DOCUMENT = PROJECT_ROOT / "docs" / "2026-09-12-g37-terskler.md"
def _declared_thresholds() -> dict[str, str]:
match = _THRESHOLD_LINE.search(README.read_text(encoding="utf-8"))
assert match is not None, (
"README.md carries no `<!-- quality-thresholds: ... -->` marker; without "
"it the published bars can drift from the ones the gate applies"
)
pairs = (token.strip().split("=") for token in match.group(1).split(","))
return {extension: share for extension, share in pairs}
def test_the_readme_names_exactly_the_thresholds_the_gate_applies() -> None:
from llm_ingestion_okf.quality import THRESHOLDS
assert _declared_thresholds() == {
extension: threshold.as_share() for extension, threshold in THRESHOLDS.items()
}
def test_the_threshold_document_carries_the_same_bars() -> None:
"""Three copies, one measurement: the code, the README and the document.
The document is where a bar's N and corpus live, so a bar that moved in the
code without moving there would publish a number nobody measured.
"""
from llm_ingestion_okf.quality import THRESHOLDS
text = THRESHOLD_DOCUMENT.read_text(encoding="utf-8")
for extension, threshold in THRESHOLDS.items():
row = f"| `{extension}` | `{threshold.metric}` | **{threshold.as_share()}** |"
assert row in text, f"{THRESHOLD_DOCUMENT.name} carries no row {row}"
def test_the_readme_quality_section_does_not_promise_a_quality_claim() -> None:
"""The one sentence that must not come back: PASS as a statement of quality."""
text = README.read_text(encoding="utf-8").lower()
assert "okf quality" in text
assert "regression bar against a pinned artifact" in text

226
tests/test_quality.py Normal file
View file

@ -0,0 +1,226 @@
"""`okf quality` -- the per-file-type verdict, and what it refuses to say.
`okf check` is a CONTRACT check: it asks whether a payload carries what a claim
must rest on. Measured 2026-09-10 by `vegnormal-okf`, it returned 0 findings and
exit 0 on three arms over one corpus whose hit@k ranged from 6 of 6 to 0 of 6.
This module is the other question -- did the cut find anything worth reading --
and its whole discipline is that it answers it PER FILE TYPE, with the
denominator printed, and never answers PASS for a type it has no measurement
for.
Each rule below is exercised in both directions on purpose: a rule that has only
ever been run against the case it fires on has not been measured, it has been
asserted. The thresholds themselves, and the four evidence corpora they were
read off, live in `docs/2026-09-12-g37-terskler.md`.
"""
from __future__ import annotations
from pathlib import Path
from llm_ingestion_okf import quality
_FRONTMATTER = """---
type: reference
title: {title}
source_file: {source_file}
ingested_at: 1970-01-01T00:00:00Z
---
{body}
"""
def _bundle(root: Path, concepts: list[tuple[str, str, str]]) -> Path:
"""A flat bundle: one root index, one file per concept.
`concepts` is `(stem, source_file, body)`. `source_file` may be the empty
string, which is the shape a bundle from another producer arrives in.
"""
root.mkdir(parents=True, exist_ok=True)
lines = ["---", "okf_version: 0.2", "bundle_id: quality-fixture", "---", ""]
for stem, source_file, body in concepts:
(root / f"{stem}.md").write_text(
_FRONTMATTER.format(title=stem, source_file=source_file, body=body),
encoding="utf-8",
newline="",
)
lines.append(f"- [{stem}]({stem}.md)")
(root / "index.md").write_text("\n".join(lines) + "\n", encoding="utf-8", newline="")
return root
def _pdf_documents(count: int, *, single: int) -> list[tuple[str, str, str]]:
"""`count` PDF documents, `single` of them yielding one concept only."""
concepts: list[tuple[str, str, str]] = []
for index in range(count):
source = f"doc-{index}.pdf"
segments = 1 if index < single else 3
for segment in range(segments):
concepts.append((f"doc-{index}-{segment}", source, "Body text enough to count."))
return concepts
def test_a_type_at_its_measured_reference_passes(tmp_path: Path) -> None:
"""The known-positive: 8 of 32 is the reference, and 8 of 32 is not worse."""
root = _bundle(tmp_path / "at-reference", _pdf_documents(32, single=8))
report = quality.measure_bundle(root)
row = report.row(".pdf")
assert row.verdict == "PASS"
assert (row.structure_null, row.documents) == (8, 32)
def test_a_type_worse_than_its_reference_fails(tmp_path: Path) -> None:
"""The known-negative for the same rule: 9 of 32 is worse than 8 of 32."""
root = _bundle(tmp_path / "over-reference", _pdf_documents(32, single=9))
report = quality.measure_bundle(root)
assert report.row(".pdf").verdict == "FAIL"
assert report.exit_code == 1
def test_an_empty_concept_fails_a_type_that_has_no_threshold(tmp_path: Path) -> None:
"""FAIL is reachable for every type; PASS is not. `.md` has no threshold."""
root = _bundle(
tmp_path / "empty-body",
[("a", "note.md", "Body text."), ("b", "note.md", " \n")],
)
report = quality.measure_bundle(root)
row = report.row(".md")
assert row.verdict == "FAIL"
assert (row.empty, row.concepts) == (1, 2)
def test_a_healthy_type_without_a_threshold_is_unmeasured_and_never_pass(
tmp_path: Path,
) -> None:
"""The other direction of the same rule: nothing wrong, and still not PASS."""
root = _bundle(
tmp_path / "no-threshold",
[("a", "note.md", "Body text."), ("b", "note.md", "More body text.")],
)
report = quality.measure_bundle(root)
assert report.row(".md").verdict == "UNMEASURED"
assert report.exit_code == 3
def test_concepts_without_a_source_file_are_unmeasured(tmp_path: Path) -> None:
"""The shape three of the four evidence corpora arrive in.
`n100-2023`, `n200-2024` and `n500-2024` carry `source_file` on 0 of 446,
0 of 1 133 and 0 of 270 concepts, so a per-file-type gate has no type to
speak about. Measured 2026-09-12; the gate says so instead of passing them.
"""
root = _bundle(tmp_path / "foreign", [("a", "", "Body text."), ("b", "", "More text.")])
report = quality.measure_bundle(root)
row = report.row(quality.NO_SOURCE_FILE)
assert row.verdict == "UNMEASURED"
assert report.exit_code == 3
def test_a_failing_type_outweighs_a_passing_one(tmp_path: Path) -> None:
concepts = _pdf_documents(32, single=9)
for index in range(5): # five documents: the floor a rate needs to be a rate
concepts.append((f"note-{index}-0", f"note-{index}.docx", "Body text."))
concepts.append((f"note-{index}-1", f"note-{index}.docx", "More body text."))
report = quality.measure_bundle(_bundle(tmp_path / "mixed", concepts))
assert report.row(".docx").verdict == "PASS"
assert report.row(".pdf").verdict == "FAIL"
assert report.exit_code == 1
def test_every_row_prints_its_denominator(tmp_path: Path) -> None:
root = _bundle(tmp_path / "printed", _pdf_documents(32, single=8))
rendered = quality.measure_bundle(root).render()
assert "8/32" in rendered
assert "0/80" in rendered # empty concepts, over the concepts of the type
assert "documents" in rendered
def test_the_bundle_run_log_is_reported_when_the_bundle_carries_one(tmp_path: Path) -> None:
"""A rejected document leaves NO row in the bundle: the log is the only trace.
On the pinned K2 bundle the log reads `N = 43, merged 39, coded rejections
4`, and the bundle itself shows 39 documents -- so the gate's own
denominator is the bundle's, never the corpus's, and it says which one it
is using.
"""
root = _bundle(tmp_path / "logged", _pdf_documents(2, single=0))
(root / "log.md").write_text(
"* **Ingested**: /x - N = 3 (the corpus directory's file count, computed at "
"run time), merged = 2 (2 substantive, 0 degenerate), coded rejections = 1.\n",
encoding="utf-8",
newline="",
)
report = quality.measure_bundle(root)
assert report.run_log == "N = 3, merged = 2, coded rejections = 1"
assert "coded rejections = 1" in report.render()
def test_a_bundle_without_a_log_says_so_rather_than_reporting_zero(tmp_path: Path) -> None:
root = _bundle(tmp_path / "unlogged", _pdf_documents(2, single=0))
report = quality.measure_bundle(root)
assert report.run_log is None
assert "no run log" in report.render()
def test_a_missing_bundle_exits_two(tmp_path: Path, capsys) -> None: # type: ignore[no-untyped-def]
assert quality.main([str(tmp_path / "nowhere")]) == 2
def test_the_cli_reaches_the_gate_through_okf_quality(tmp_path: Path, capsys) -> None: # type: ignore[no-untyped-def]
from llm_ingestion_okf import cli
root = _bundle(tmp_path / "through-cli", _pdf_documents(32, single=9))
assert cli.main(["quality", str(root)]) == 1
assert ".pdf" in capsys.readouterr().out
def test_every_threshold_names_the_corpus_and_the_denominator_it_was_read_off() -> None:
"""A threshold without an N is the thing this whole gate exists to refuse."""
assert quality.THRESHOLDS, "no thresholds at all would make every type UNMEASURED"
for extension, threshold in quality.THRESHOLDS.items():
assert extension.startswith("."), extension
assert threshold.documents >= quality.MIN_DOCUMENTS_FOR_A_THRESHOLD
assert threshold.source, f"{extension} threshold names no source"
assert threshold.limit_documents == threshold.documents
def test_a_rate_over_too_few_documents_is_not_a_rate(tmp_path: Path) -> None:
"""FOUND BY RUNNING THE GATE, not by reading it.
`~/repos/vegnormal-okf/build/sk2-bundle-default` is one PDF cut into 2 182
concepts. Against the 32-document reference its one-concept share is 0 of 1,
which the first version of this rule read as PASS -- a verdict resting on a
denominator of one, which is the exact failure `MIN_DOCUMENTS_FOR_A_THRESHOLD`
exists to refuse. The floor binds the BUNDLE's denominator too, not only the
threshold's.
"""
root = _bundle(tmp_path / "one-document", _pdf_documents(1, single=0))
assert quality.measure_bundle(root).row(".pdf").verdict == "UNMEASURED"
def test_the_floor_admits_a_bundle_that_reaches_it(tmp_path: Path) -> None:
"""The other direction: five documents is the floor, and five is enough."""
root = _bundle(tmp_path / "five-documents", _pdf_documents(5, single=1))
assert quality.measure_bundle(root).row(".pdf").verdict == "PASS"
def test_an_empty_concept_fails_even_below_the_floor(tmp_path: Path) -> None:
"""The empty-body bar is per concept, not a rate, so the floor does not gate it."""
root = _bundle(tmp_path / "one-document-empty", [("a", "only.pdf", " \n")])
assert quality.measure_bundle(root).row(".pdf").verdict == "FAIL"
def test_the_no_source_file_row_reports_no_document_count(tmp_path: Path) -> None:
"""A row that is not a file type has no documents either.
Every concept without the key shares the same empty `source_file`, so a
naive grouping reports `documents 1` and `one-concept documents 0/1` for a
bundle of 446 concepts -- three numbers that look measured and mean nothing.
"""
root = _bundle(tmp_path / "foreign-render", [("a", "", "Body."), ("b", "", "More.")])
rendered = quality.measure_bundle(root).render()
assert "documents 1" not in rendered
assert "one-concept documents" not in rendered
assert "empty 0/2" in rendered
assert "no source_file on 2 of 2 concepts" in rendered