table-stitcher 0.4.2__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/CHANGELOG.md +17 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/PKG-INFO +1 -1
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/pyproject.toml +1 -1
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/adapters/docling.py +51 -5
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/conftest.py +9 -1
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/15-page-druglist.corp.expected.yaml +3 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/covid-misc-labs-4pg.pt2.expected.yaml +3 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/retirement-portfolio.corp.expected.yaml +3 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/test_docling_adapter.py +71 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.claude/settings.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/dependabot.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/pull_request_template.md +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/workflows/ci.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/workflows/release.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.github/workflows/upstream-smoke.yml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.gitignore +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/.pre-commit-config.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/CONTRIBUTING.md +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/LICENSE +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/README.md +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/SECURITY.md +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/examples/basic_pipeline.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/examples/system_controller.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/scripts/regenerate_docling_snapshots.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/scripts/release_gate.sh +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/__init__.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/adapters/README.md +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/adapters/__init__.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/adapters/base.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/merger.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/models.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/src/table_stitcher/py.typed +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/README.md +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/__init__.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/fixtures/tablemeta/headerless-width-drift.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/__init__.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/_tools/__init__.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/_tools/regenerate_expected.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/_synth/__init__.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/_synth/generate.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/.gitkeep +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/kaop-study-mixed-3pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/kaop-study-mixed-3pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/kaop-study-mixed-3pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/lab-panels-3pg.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/lab-panels-3pg.corp.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/distinct-tables-no-merge/lab-panels-3pg.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/false-merge/category-rows-thematic-2pg.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/false-merge/category-rows-thematic-2pg.corp.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/false-merge/category-rows-thematic-2pg.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/15-page-druglist.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/15-page-druglist.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/gene-symbols-6pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/gene-symbols-6pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/gene-symbols-6pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/rct-study-table-5pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/rct-study-table-5pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/rct-study-table-5pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/rrt-outcomes-2pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/rrt-outcomes-2pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/rrt-outcomes-2pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/uveitis-case-series-5pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/uveitis-case-series-5pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/headerless-continuation/uveitis-case-series-5pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/covid-misc-labs-4pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/covid-misc-labs-4pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/lit-review-3pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/lit-review-3pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/lit-review-3pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/retirement-portfolio.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/inconsistent-header-detection/retirement-portfolio.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/.gitkeep +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/biological-process-2pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/biological-process-2pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/biological-process-2pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/symptoms-mediators-4pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/symptoms-mediators-4pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/loose-header-layout/symptoms-mediators-4pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/multilingual/corporate-history-2pg.edinet.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/multilingual/corporate-history-2pg.edinet.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/multilingual/corporate-history-2pg.edinet.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/multilingual/subsidiaries-4pg.edinet.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/multilingual/subsidiaries-4pg.edinet.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/multilingual/subsidiaries-4pg.edinet.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/orphan-pair/.gitkeep +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/orphan-pair/varicose-veins-new-table-header-7pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/orphan-pair/varicose-veins-new-table-header-7pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/orphan-pair/varicose-veins-new-table-header-7pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/page-gap-too-large/unrelated-tables-gap4.synth.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/page-gap-too-large/unrelated-tables-gap4.synth.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/page-gap-too-large/unrelated-tables-gap4.synth.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/4-page-substance-list.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/4-page-substance-list.corp.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/4-page-substance-list.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/cell-markers-4pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/cell-markers-4pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/cell-markers-4pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/challenge-categories-2pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/challenge-categories-2pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/challenge-categories-2pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/fungal-taxonomy-4pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/fungal-taxonomy-4pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/fungal-taxonomy-4pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/rowspan-insurance-payout.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/rowspan-insurance-payout.corp.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/rowspan-insurance-payout.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/search-strategies-2pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/search-strategies-2pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/search-strategies-2pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/study-sample-7pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/study-sample-7pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/study-sample-7pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/themes-exemplars-3pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/themes-exemplars-3pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/repeated-header/themes-exemplars-3pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/simple-continuation/sample-table.corp.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/simple-continuation/sample-table.corp.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/simple-continuation/sample-table.corp.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/spillover/note-overflow.synth.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/spillover/note-overflow.synth.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/spillover/note-overflow.synth.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/width-drift/.gitkeep +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/width-drift/abx-literature-review-7pg.pt2.docling.json +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/width-drift/abx-literature-review-7pg.pt2.expected.yaml +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/width-drift/abx-literature-review-7pg.pt2.pdf +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/test_fixtures.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/test_intervening_content_guard.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/test_merger.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/test_public_api.py +0 -0
- {table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/test_tablemeta_fixtures.py +0 -0
|
@@ -7,6 +7,23 @@ the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.3] — 2026-06-11
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Reprinted continuation-page headers appended as data rows on multi-page
|
|
15
|
+
merge** (`adapters/docling.py`). When a table's column header is reprinted at
|
|
16
|
+
the top of each page — especially a multi-row (hierarchical) header — the
|
|
17
|
+
repeated header rows survived the merge as bogus data rows, misaligning the
|
|
18
|
+
stitched table. Injection now drops a body row when it is *both* flagged
|
|
19
|
+
`column_header` by Docling *and* a tokenized match (Jaccard ≥ 0.6) for the
|
|
20
|
+
reconstructed header block. Both signals are required: the flag alone is
|
|
21
|
+
unreliable (Docling over-flags rowspan/continuation *data* rows as headers),
|
|
22
|
+
and the tokenized comparison is punctuation-agnostic, so per-cell OCR drift
|
|
23
|
+
such as `(S$)` vs `($$)` is tolerated without any threshold tuning. The merged
|
|
24
|
+
DataFrame (`lt.df`) is unchanged; only the injected document is de-duplicated.
|
|
25
|
+
A `debug` log reports each dropped row.
|
|
26
|
+
|
|
10
27
|
## [0.4.2] — 2026-06-08
|
|
11
28
|
|
|
12
29
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: table-stitcher
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Reassemble tables split across page boundaries in PDF extraction
|
|
5
5
|
Project-URL: Homepage, https://github.com/pebbleroad/table-stitcher
|
|
6
6
|
Project-URL: Repository, https://github.com/pebbleroad/table-stitcher
|
|
@@ -499,6 +499,42 @@ def _reemit_body_row(
|
|
|
499
499
|
return grid_row, distinct
|
|
500
500
|
|
|
501
501
|
|
|
502
|
+
# Jaccard threshold for recognizing a body row as a reprinted continuation
|
|
503
|
+
# header. Combined with Docling's column_header flag — both must hold — so it
|
|
504
|
+
# stays conservative. Matches the merger's header_sim_strict default; the
|
|
505
|
+
# punctuation-agnostic tokenizer makes it tolerant of per-cell OCR drift such
|
|
506
|
+
# as "(S$)" vs "($$)".
|
|
507
|
+
_REPEATED_HEADER_SIM = 0.6
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _row_token_set(cells: list[TableCell]) -> set:
|
|
511
|
+
"""Union of tokenized cell text for a grid row (duplicate span cells fold in)."""
|
|
512
|
+
toks: set = set()
|
|
513
|
+
for c in cells:
|
|
514
|
+
if c:
|
|
515
|
+
toks |= tokenize(getattr(c, "text", "") or "")
|
|
516
|
+
return toks
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def _is_reprinted_header(orig_row: list[TableCell], header_sigs: list[set]) -> bool:
|
|
520
|
+
"""True if ``orig_row`` is a reprinted continuation header to drop from the body.
|
|
521
|
+
|
|
522
|
+
Docling reprints the column header at the top of each continuation page; on
|
|
523
|
+
a multi-row header those rows survive the merge as bogus data rows. They are
|
|
524
|
+
dropped only when BOTH signals agree: Docling flagged the row
|
|
525
|
+
``column_header`` AND it is a tokenized match for one of the reconstructed
|
|
526
|
+
header-block rows. The flag alone is unreliable — Docling over-flags
|
|
527
|
+
rowspan/continuation *data* rows as headers — so the content match guards
|
|
528
|
+
against deleting real data.
|
|
529
|
+
"""
|
|
530
|
+
if not any(getattr(c, "column_header", False) for c in orig_row if c):
|
|
531
|
+
return False
|
|
532
|
+
toks = _row_token_set(orig_row)
|
|
533
|
+
if not toks:
|
|
534
|
+
return False
|
|
535
|
+
return any(jaccard(toks, sig) >= _REPEATED_HEADER_SIM for sig in header_sigs)
|
|
536
|
+
|
|
537
|
+
|
|
502
538
|
def _dataframe_to_docling_data(
|
|
503
539
|
df: pd.DataFrame,
|
|
504
540
|
original_data: Optional[TableData] = None,
|
|
@@ -587,12 +623,15 @@ def _dataframe_to_docling_data(
|
|
|
587
623
|
|
|
588
624
|
# --- Build data rows from merged DataFrame ---
|
|
589
625
|
# Index member fragments' original body rows so spanning cells survive the
|
|
590
|
-
# round-trip (see
|
|
626
|
+
# round-trip (see _index_member_rows).
|
|
591
627
|
body_index = _index_member_rows(member_data) if member_data else {}
|
|
628
|
+
# Tokenized signatures of the reconstructed header block, for dropping
|
|
629
|
+
# reprinted continuation headers (see _is_reprinted_header).
|
|
630
|
+
header_sigs = [s for s in (_row_token_set(h) for h in orig_header_rows) if s]
|
|
592
631
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
632
|
+
emitted = 0
|
|
633
|
+
for _, row in df.iterrows():
|
|
634
|
+
table_row_idx = num_header_rows + emitted
|
|
596
635
|
row_vals = ["" if (pd.isna(v) or v is None) else str(v) for v in row]
|
|
597
636
|
|
|
598
637
|
# Re-emit untouched rows from their original grid cells (preserves
|
|
@@ -601,9 +640,15 @@ def _dataframe_to_docling_data(
|
|
|
601
640
|
bucket = body_index.get(tuple(row_vals))
|
|
602
641
|
if bucket:
|
|
603
642
|
orig_row = bucket.pop(0)
|
|
643
|
+
if header_sigs and _is_reprinted_header(orig_row, header_sigs):
|
|
644
|
+
# Reprinted header from a continuation page — already present as
|
|
645
|
+
# the header block; drop it instead of duplicating into the body.
|
|
646
|
+
log.debug("Dropped reprinted continuation header row from merged body.")
|
|
647
|
+
continue
|
|
604
648
|
grid_row, distinct = _reemit_body_row(orig_row, table_row_idx, has_row_headers)
|
|
605
649
|
grid.append(grid_row)
|
|
606
650
|
table_cells.extend(distinct)
|
|
651
|
+
emitted += 1
|
|
607
652
|
continue
|
|
608
653
|
|
|
609
654
|
grid_row: list[TableCell] = []
|
|
@@ -625,8 +670,9 @@ def _dataframe_to_docling_data(
|
|
|
625
670
|
table_cells.append(cell)
|
|
626
671
|
|
|
627
672
|
grid.append(grid_row)
|
|
673
|
+
emitted += 1
|
|
628
674
|
|
|
629
|
-
num_total_rows = num_header_rows +
|
|
675
|
+
num_total_rows = num_header_rows + emitted
|
|
630
676
|
|
|
631
677
|
return TableData(num_rows=num_total_rows, num_cols=num_cols, table_cells=table_cells, grid=grid)
|
|
632
678
|
|
|
@@ -333,7 +333,15 @@ def assert_public_stitch_injects_docling_doc(
|
|
|
333
333
|
ctx = f"public stitch for members={members}, pages={exp['pages']}"
|
|
334
334
|
|
|
335
335
|
assert getattr(anchor.data, "num_rows", 0) > 0, f"{ctx}: anchor has no data"
|
|
336
|
-
if "
|
|
336
|
+
if "injected_rows" in exp:
|
|
337
|
+
# Exact injected row count. Used when injection legitimately differs
|
|
338
|
+
# from the parser-neutral shape — e.g. reprinted continuation-page
|
|
339
|
+
# headers are dropped from the body (they remain in the merged
|
|
340
|
+
# DataFrame but are not duplicated into the stitched document).
|
|
341
|
+
assert anchor.data.num_rows == exp["injected_rows"], (
|
|
342
|
+
f"{ctx}: injected rows {anchor.data.num_rows} != expected {exp['injected_rows']}"
|
|
343
|
+
)
|
|
344
|
+
elif "shape" in exp:
|
|
337
345
|
# +1 or more for header rows; this guards that merged data was injected.
|
|
338
346
|
assert anchor.data.num_rows >= exp["shape"][0] + 1, (
|
|
339
347
|
f"{ctx}: anchor rows {anchor.data.num_rows} do not contain merged body"
|
|
@@ -37,6 +37,9 @@ logical_tables:
|
|
|
37
37
|
shape:
|
|
38
38
|
- 500
|
|
39
39
|
- 3
|
|
40
|
+
# Injection drops the reprinted header block (title + column names) that the
|
|
41
|
+
# merged DataFrame still carries as its first two rows: 500 - 2 = header(2) + body(498).
|
|
42
|
+
injected_rows: 500
|
|
40
43
|
columns:
|
|
41
44
|
- Column_0
|
|
42
45
|
- Column_1
|
|
@@ -388,6 +388,77 @@ class TestBodySpanPreservation:
|
|
|
388
388
|
assert all(cell.col_span == 1 for cell in td.grid[1])
|
|
389
389
|
|
|
390
390
|
|
|
391
|
+
class TestReprintedHeaderDedup:
|
|
392
|
+
"""Reprinted continuation-page headers are dropped from the injected body,
|
|
393
|
+
but column_header-flagged rows that don't match the header (Docling
|
|
394
|
+
over-flagging rowspan/continuation data) are kept.
|
|
395
|
+
"""
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def _cell(text, r, c, *, header):
|
|
399
|
+
return TableCell(
|
|
400
|
+
text=text,
|
|
401
|
+
row_span=1,
|
|
402
|
+
col_span=1,
|
|
403
|
+
column_header=header,
|
|
404
|
+
row_header=False,
|
|
405
|
+
start_row_offset_idx=r,
|
|
406
|
+
end_row_offset_idx=r + 1,
|
|
407
|
+
start_col_offset_idx=c,
|
|
408
|
+
end_col_offset_idx=c + 1,
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
def _table(self, rows: list) -> TableData:
|
|
412
|
+
grid = []
|
|
413
|
+
flat = []
|
|
414
|
+
for r, (cells_text, is_header) in enumerate(rows):
|
|
415
|
+
grid_row = [self._cell(t, r, c, header=is_header) for c, t in enumerate(cells_text)]
|
|
416
|
+
grid.append(grid_row)
|
|
417
|
+
flat.extend(grid_row)
|
|
418
|
+
return TableData(num_rows=len(rows), num_cols=len(rows[0][0]), table_cells=flat, grid=grid)
|
|
419
|
+
|
|
420
|
+
def test_drops_reprinted_header_keeps_misflagged_data(self):
|
|
421
|
+
# Anchor: 1-row header "(S$)" + one data row.
|
|
422
|
+
anchor = self._table([(["SECTION", "LIMIT (S$)"], True), (["Death", "100"], False)])
|
|
423
|
+
# Continuation: header reprinted with OCR drift "($$)", a data row, and
|
|
424
|
+
# a row Docling wrongly flagged column_header (real data, distinct text).
|
|
425
|
+
satellite = self._table(
|
|
426
|
+
[
|
|
427
|
+
(["SECTION", "LIMIT ($$)"], True),
|
|
428
|
+
(["Injury", "50"], False),
|
|
429
|
+
(["Sub-limit per accident", "999"], True),
|
|
430
|
+
]
|
|
431
|
+
)
|
|
432
|
+
# Merged DataFrame: the merger concatenates everything, including the
|
|
433
|
+
# reprinted header and the mis-flagged row.
|
|
434
|
+
merged_df = pd.DataFrame(
|
|
435
|
+
[
|
|
436
|
+
["Death", "100"],
|
|
437
|
+
["SECTION", "LIMIT ($$)"],
|
|
438
|
+
["Injury", "50"],
|
|
439
|
+
["Sub-limit per accident", "999"],
|
|
440
|
+
],
|
|
441
|
+
columns=["Column_0", "Column_1"],
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
td = _dataframe_to_docling_data(
|
|
445
|
+
merged_df, original_data=anchor, member_data=[anchor, satellite]
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
body = [r for r in td.grid if not any(getattr(c, "column_header", False) for c in r if c)]
|
|
449
|
+
body_text = " ".join(str(c.text) for r in body for c in r if c)
|
|
450
|
+
|
|
451
|
+
# Reprinted header (drifted) dropped from the body...
|
|
452
|
+
assert "($$)" not in body_text
|
|
453
|
+
# ...but the header block still carries the anchor's "(S$)".
|
|
454
|
+
assert any("(S$)" in (c.text or "") for r in td.grid for c in r if c)
|
|
455
|
+
# Real data preserved, including the column_header-mis-flagged row.
|
|
456
|
+
assert "Death" in body_text and "Injury" in body_text
|
|
457
|
+
assert "Sub-limit per accident" in body_text and "999" in body_text
|
|
458
|
+
# 1 header row + 3 body rows (one reprinted header removed from 4).
|
|
459
|
+
assert td.num_rows == 4
|
|
460
|
+
|
|
461
|
+
|
|
391
462
|
class TestAdapterProtocol:
|
|
392
463
|
"""Verify DoclingAdapter satisfies the protocol."""
|
|
393
464
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/fixtures/tablemeta/headerless-width-drift.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/_tools/regenerate_expected.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/orphan-pair/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{table_stitcher-0.4.2 → table_stitcher-0.4.3}/tests/integration/fixtures/width-drift/.gitkeep
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|