feat(propose,cli): the coverage gap had one cause, and round 7's own decomposition did not reproduce
Round 7 named two open items: a table-block candidate displacing a declared
heading (26 041 characters between entries, `md` at 3 of 4 declared headings)
and 17 590 characters after the last entry, never examined. Measured on
`a364ef4`, the first premise does not reproduce and the second is not a
separate mechanism.
`md` recovers 4 of 4, not 3 of 4. D1 (`--keep-table-heading`) is the repair
for exactly the mechanism round 7 § 5 describes, and round 7 moved it into
the default in the same commit -- so § 5 is a pre-move measurement presented
as the post-move state. That is round 7's own trap, stated in its own report:
a number about a rule is a measurement of a configuration. Nothing held the
cell, which is why it could be wrong and stay wrong.
The remaining gap has ONE cause. Every rule closes a span against the NEXT
MARK; three steps then remove a mark after its neighbour's `end` was fixed
against it. The orphan check leaks 18 527 characters over 15 of 39 documents,
`fold_units` clause 1 leaks 7 514 between entries, and the same clause on the
last run leaks all 17 590 tail characters -- with `unit_fold=False` the corpus
tail gap is 0. Round 6 already established the principle (filter at admission,
let the mark above carry the text) and it was never applied post-filter.
`--close-span-gaps` states it once, after the fold, where every removal has
happened: a span runs to the next SURVIVOR, the last to the end of the text.
It adds no boundary. Measured: 43 631 characters (2.51 %) -> 0, entries
429 = 429, K2 concepts 436 = 436, the operator's folder 52 md = 52.
All four acceptance conditions hold at once. The 12-position reference is
label-identical -- 11 of 12 under |F|[3]=12 and 10 of 12 under |F|[3]=11,
both readings reported, not one position moved. hit@8 holds [1,1,1,1,1,-] on
the round 8 bundle (436), the round 7 default (436) and Arm B (629), and the
known-negative reproduces on the new bytes: `--no-tie-shared-rank` gives
[2,1,1,1,1,-].
The pin is rebuilt against the default that actually ships
(`K2-bundle-default-20260911`, 832 files, digest 8c93e5e3...); two independent
builds of it differ in nothing, `log.md` included.
Two measurement failures are recorded rather than hidden. The corpus identity
check first used `xargs` without `-print0`, every filename split on its
spaces, `shasum` read nothing, and both sides agreed on a digest of the
failure; a control against the digest of empty input separated them. And the
smoke build cannot prove the flag arrives -- the operator's folder has a
coverage gap of zero already, so `diff -rq` is 0 either way; the plumbing has
its own test on a document that has the defect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a364ef4c7d
commit
956714594d
8 changed files with 592 additions and 22 deletions
100
tests/test_md_declared_headings.py
Normal file
100
tests/test_md_declared_headings.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
"""`md` recovers every heading it declares, and D1 is what makes that true.
|
||||
|
||||
Round 7 reported this cell as **3 of 4**, with the mechanism decomposed: a
|
||||
`rule:table-block` candidate opens BELOW `## 3 Prising` instead of a
|
||||
`rule:heading` opening ON it, so the heading is bodiless, the orphan check
|
||||
drops it, and the fold then merges the nameless block into the section above.
|
||||
|
||||
Re-measured on `a364ef4` (round 7's own commit), the default recovers **4 of
|
||||
4**. The report's § 5 is a measurement of the configuration that existed
|
||||
BEFORE the same round moved `--keep-table-heading` into the default -- D1 is
|
||||
precisely the repair for that mechanism, and it shipped in the commit the
|
||||
report describes. Nothing in the tree said so, because the decomposition and
|
||||
the default move live in different files.
|
||||
|
||||
That is the round-7 trap restated: a number about a rule is a measurement of a
|
||||
CONFIGURATION. This file is the missing pin. The cell is asserted here, and so
|
||||
is its cause -- turn D1 off and the third heading becomes a table block again.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from llm_ingestion_okf import cli
|
||||
from llm_ingestion_okf.propose import RULE_HEADING, RULE_TABLE_BLOCK, find_candidates
|
||||
|
||||
DEFAULT = dict(
|
||||
outline_run=cli.DEFAULT_OUTLINE_RUN,
|
||||
table_grid=cli.DEFAULT_TABLE_GRID,
|
||||
unit_fold=cli.DEFAULT_UNIT_FOLD,
|
||||
keep_table_heading=cli.DEFAULT_KEEP_TABLE_HEADING,
|
||||
sheet_section_rows=cli.DEFAULT_SHEET_SECTION_ROWS,
|
||||
drop_wrapped_outline=cli.DEFAULT_DROP_WRAPPED_OUTLINE,
|
||||
outline_gate=cli.DEFAULT_OUTLINE_GATE,
|
||||
first_span_from_zero=cli.DEFAULT_FIRST_SPAN_FROM_ZERO,
|
||||
close_span_gaps=cli.DEFAULT_CLOSE_SPAN_GAPS,
|
||||
)
|
||||
|
||||
#: The same structure round 7 built: three chapters, the last one a heading
|
||||
#: with a table directly under it and a closing line after the table.
|
||||
DOCUMENT = """# Prosjektbeskrivelse Testdokument
|
||||
|
||||
Dette dokumentet har en kjent struktur: tre kapitler paa niva 1, hvert med
|
||||
en kort brodtekst, samt en innledning over det forste kapitlet.
|
||||
|
||||
## 1 Omfang
|
||||
|
||||
Leveransen omfatter tre delytelser. Hver delytelse har egen frist og egen
|
||||
akseptansetest. Teksten her er brodtekst og skal ikke bli en overskrift.
|
||||
|
||||
## 2 Krav til dokumentasjon
|
||||
|
||||
Dokumentasjon leveres i PDF. Tegninger leveres i DWG. Modeller leveres i IFC.
|
||||
Krav nummer 2.1 gjelder alle tre formatene.
|
||||
|
||||
## 3 Prising
|
||||
|
||||
| Post | Beskrivelse | Enhet | Mengde |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Riving av eksisterende dekke | m2 | 420 |
|
||||
| 2 | Ny baerekonstruksjon | tonn | 38 |
|
||||
| 3 | Tekniske installasjoner | RS | 1 |
|
||||
|
||||
Prisene fylles ut av tilbyder ved tilbudsfrist.
|
||||
"""
|
||||
|
||||
DECLARED = (
|
||||
"Prosjektbeskrivelse Testdokument",
|
||||
"1 Omfang",
|
||||
"2 Krav til dokumentasjon",
|
||||
"3 Prising",
|
||||
)
|
||||
|
||||
|
||||
def _declared_headings(**kwargs: object) -> list[str]:
|
||||
candidates = find_candidates(DOCUMENT, **{**DEFAULT, **kwargs}) # type: ignore[arg-type]
|
||||
return [c.title for c in candidates if c.rule == RULE_HEADING]
|
||||
|
||||
|
||||
def test_the_default_recovers_all_four_declared_headings() -> None:
|
||||
assert _declared_headings() == list(DECLARED)
|
||||
|
||||
|
||||
def test_the_priced_section_is_a_heading_and_not_a_table_block() -> None:
|
||||
"""The specific cell: `## 3 Prising` keeps its own line and its own name."""
|
||||
candidates = find_candidates(DOCUMENT, **DEFAULT)
|
||||
prising = [c for c in candidates if c.title == "3 Prising"]
|
||||
assert len(prising) == 1
|
||||
assert prising[0].rule == RULE_HEADING
|
||||
assert DOCUMENT[prising[0].start : prising[0].end].startswith("## 3 Prising")
|
||||
|
||||
|
||||
def test_without_d1_the_cell_falls_back_to_three_of_four() -> None:
|
||||
"""The known-negative: the pin names its own cause instead of being green
|
||||
for an unstated reason. Without `--keep-table-heading` the heading is
|
||||
orphaned and a table block carries its name from a line below it."""
|
||||
assert _declared_headings(keep_table_heading=False) == list(DECLARED[:3])
|
||||
candidates = find_candidates(DOCUMENT, **{**DEFAULT, "keep_table_heading": False})
|
||||
carrier = [c for c in candidates if c.title == "3 Prising"]
|
||||
assert len(carrier) == 1
|
||||
assert carrier[0].rule == RULE_TABLE_BLOCK
|
||||
assert not DOCUMENT[carrier[0].start : carrier[0].end].startswith("## 3 Prising")
|
||||
Loading…
Add table
Add a link
Reference in a new issue