docs(accounting): record the operator's two decisions of 2026-09-17
The content accounting stays OPT-IN until the losses it reports on the reference corpus are fixed; a default-on door would fail builds that pass today. Of the three exceptions the gate proposed, only the PDF one (heading, paragraph, table) is approved. Approving it moves no number, because no witness counts a heading in a PDF: what it changes is that the gap is a stated limit of the instrument rather than an open question about the build. An image in a workbook, or in md/txt/csv/json/odt/rtf, stays unaccounted and therefore stays red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
230d1cbccd
commit
751441c17d
5 changed files with 50 additions and 15 deletions
|
|
@ -33,6 +33,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
the page, and 2 docx Title paragraphs, which the converter moves into
|
||||
metadata. A default-on door would therefore fail builds that pass today.
|
||||
Report: `docs/2026-09-17-innholdsregnskapet-bygget.md`.
|
||||
- **Opt-in by operator decision (2026-09-17)**, until the losses it reports
|
||||
on the reference corpus are fixed. Of the three exceptions the gate
|
||||
proposes, the operator approved the PDF one only; approving it moves no
|
||||
number, because no witness counts a heading in a PDF.
|
||||
- **Limit, measured:** the check proves that a string is present, not
|
||||
where. Short elements such as a section label or a one-word title are
|
||||
often found elsewhere in the same document. With R761's concept text cut
|
||||
|
|
|
|||
|
|
@ -937,9 +937,12 @@ and fixtures, never code.
|
|||
from byte equality, which would credit R761's 7 unpointed duplicates.
|
||||
- **R761 is refused whole by guard 1.4.0 because of its 71 U+00AD soft
|
||||
hyphens.** That is the security repo's question, and it has been asked.
|
||||
- Three exceptions are PROPOSED and none is approved: PDF
|
||||
headings/paragraphs/tables, xlsx images, and images in md, txt, csv,
|
||||
json, odt and rtf.
|
||||
- **Operator 2026-09-17:** the door stays OPT-IN until the reference
|
||||
corpus's losses are fixed, and of the three proposed exceptions only the
|
||||
PDF one (heading, paragraph, table) is APPROVED. It moves no number --
|
||||
no witness counts a heading in a PDF -- so what it changes is that the
|
||||
gap is a stated limit. An image in xlsx, md, txt, csv, json, odt or rtf
|
||||
stays unaccounted, and therefore red.
|
||||
- Reports: `docs/2026-09-17-innholdsregnskapet-rod-gate.md`,
|
||||
`docs/2026-09-17-innholdsregnskapet-bygget.md`.
|
||||
- **Judge a bundle: `okf quality <bundle>` (G37, 2026-09-12).** A per-file-type
|
||||
|
|
|
|||
|
|
@ -362,6 +362,14 @@ short element such as a section label can be found elsewhere in the same
|
|||
document. The judge is `tools/okf_accounting_gate.py`, which compares the
|
||||
inventory against an independent witness.
|
||||
|
||||
**Two operator decisions, 2026-09-17.** The accounting stays OPT-IN until the
|
||||
losses it reports on the reference corpus are fixed, because a default-on door
|
||||
would fail builds that pass today. And of the three exceptions the gate
|
||||
proposed, only the PDF one is approved: a PDF without a structure tree
|
||||
declares no heading, paragraph or table, so no witness can count them. An
|
||||
image in a workbook, or in md, txt, csv, json, odt or rtf, stays unaccounted
|
||||
and therefore stays red.
|
||||
|
||||
Two things hold with or without the flag:
|
||||
|
||||
- `okf build` exits 1 when it extracted at least one document and persisted
|
||||
|
|
|
|||
|
|
@ -380,8 +380,25 @@ def test_row6_without_its_source_is_red_locally_and_skipped_in_ci(tmp_path: Path
|
|||
|
||||
|
||||
def test_the_proposed_exceptions_are_not_applied() -> None:
|
||||
assert gate.APPROVED_EXCEPTIONS == frozenset()
|
||||
assert "NOT APPROVED" in gate.render([])
|
||||
assert not {suffix for suffix, _ in gate.APPROVED_EXCEPTIONS} & {
|
||||
item["suffix"] for item in gate.PROPOSED_EXCEPTIONS
|
||||
}, "an exception cannot be both proposed and approved"
|
||||
|
||||
|
||||
def test_the_operator_approved_the_pdf_exception_and_nothing_else() -> None:
|
||||
"""Operator 2026-09-17, answering the gate's three proposals: the PDF one
|
||||
only. It moves no number -- no witness counts a heading in a PDF -- so what
|
||||
it changes is that the gap is a stated limit rather than an open question.
|
||||
"""
|
||||
assert gate.APPROVED_EXCEPTIONS == frozenset(
|
||||
{(".pdf", "heading"), (".pdf", "paragraph"), (".pdf", "table")}
|
||||
)
|
||||
assert [item["suffix"] for item in gate.PROPOSED_EXCEPTIONS] == [
|
||||
".xlsx",
|
||||
".md .txt .csv .json .odt .rtf",
|
||||
]
|
||||
assert gate.APPROVED_ON in gate.render([])
|
||||
|
||||
|
||||
def test_bad_usage_exits_two() -> None:
|
||||
|
|
|
|||
|
|
@ -100,19 +100,20 @@ RED = "RED"
|
|||
SKIPPED = "SKIPPED"
|
||||
DIAGNOSTIC = "DIAGNOSTIC"
|
||||
|
||||
#: Exceptions the operator has approved, by (suffix, element). Empty: the
|
||||
#: first time an exception arises it is the operator's to approve.
|
||||
APPROVED_EXCEPTIONS: frozenset[tuple[str, str]] = frozenset()
|
||||
#: Exceptions the operator has APPROVED, by (suffix, element), with the date.
|
||||
#: Approving one does not move a number here: the witness counts no such
|
||||
#: element in the first place, which is the whole reason the exception exists.
|
||||
#: What approval changes is that the gap is now a stated limit of this
|
||||
#: instrument rather than an open question about the build.
|
||||
APPROVED_EXCEPTIONS: frozenset[tuple[str, str]] = frozenset(
|
||||
{(".pdf", "heading"), (".pdf", "paragraph"), (".pdf", "table")}
|
||||
)
|
||||
|
||||
#: When, and by whom.
|
||||
APPROVED_ON = "2026-09-17, operator"
|
||||
|
||||
#: Exceptions this gate PROPOSES. Listed in every run; none of them is applied.
|
||||
PROPOSED_EXCEPTIONS: tuple[dict[str, str], ...] = (
|
||||
{
|
||||
"suffix": ".pdf",
|
||||
"element": "heading, paragraph, table",
|
||||
"reason": "a PDF without a structure tree declares none of them, so no "
|
||||
"witness can count them; the witness counts pages and image placements",
|
||||
"carried_instead": "the page text, with headings recovered by rule",
|
||||
},
|
||||
{
|
||||
"suffix": ".xlsx",
|
||||
"element": "image",
|
||||
|
|
@ -633,7 +634,9 @@ def render(rows: list[Row]) -> str:
|
|||
f" - {item['suffix']} {item['element']}: {item['reason']}; "
|
||||
f"carried instead: {item['carried_instead']}"
|
||||
)
|
||||
lines.append(f"approved exceptions: {len(APPROVED_EXCEPTIONS)}")
|
||||
lines.append(f"exceptions approved ({APPROVED_ON}), and none moves a denominator:")
|
||||
for suffix, element in sorted(APPROVED_EXCEPTIONS) or [("(none)", "")]:
|
||||
lines.append(f" - {suffix} {element}")
|
||||
failing = [str(r.number) for r in rows if r.fails]
|
||||
lines.append("")
|
||||
lines.append(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue