Skip to content

Multi-row table headers + .xls support + DECO benchmark (and excel_parser rename)#14

Merged
arnav2 merged 2 commits into
mainfrom
arnav2/table-header-split-fix
Jul 16, 2026
Merged

Multi-row table headers + .xls support + DECO benchmark (and excel_parser rename)#14
arnav2 merged 2 commits into
mainfrom
arnav2/table-header-split-fix

Conversation

@arnav2

@arnav2 arnav2 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Scope (large — see breakdown)

This branch bundles a repo-wide rename with three parser improvements. The rename dominates the file count; the substantive logic changes are small and isolated.

1. Package rename: ks_xlsx_parserexcel_parser (and ks_xlsx_coreexcel_core)

Mechanical rename across source, docs, scripts, site, and the Rust crate. Most of the +/- churn.

2. Multi-row table headers (the headline fix)

find_header_span previously returned a single row by construction (HeaderSpan(top=r, bottom=r)), so it could never cover a 2–4 row header — 36% of real headers. It now extends the band downward over contiguous multi-column, styled label rows, stopping at the first data row / blank / single-cell divider, bounded by MAX_HEADER_ROWS.

The styling gate is the key design choice: genuine stacked headers are uniformly bold/filled; the first data row under a one-row header is not — so single-row headers stay one row.

Measured on DECO (852 annotated .xlsx, 1,480 ground-truth tables; scripts/eval_deco.py):

cohort metric before after
multi-row headers (36%) F1 / exact 0.37 / 0% 0.50 / 24%
multi-row headers recall 0.23 0.33
single-row headers (64%) exact 84% 79%
all headers F1 / exact 0.58 / 54% 0.63 / 59%
table-boundary IoU mean 0.507 0.507 (unchanged)

A more aggressive un-gated variant reached multi-row F1 0.71 but collapsed single-row precision 0.92→0.43 (it glues data rows into headers) — rejected. New regression tests in tests/test_header_detector.py lock both behaviours.

3. Legacy .xls support

convert_xls_to_xlsx backend (xls_converter.py) so .xls workbooks parse via workbook_parser.

4. DECO structural benchmark (scripts/eval_deco.py)

Scores table-boundary IoU + header-row P/R/F1 for ks vs Docling — the structural ground truth SpreadsheetBench lacks (it only has answer positions). Wired into download_corpora.sh + benchmarks README. Notable cross-parser finding: Docling has no A1 coordinates for xlsx and over-segments 80% of sheets into tiny spurious tables (median 5/sheet, up to 624), where ks produces real boundaries at mean IoU 0.51.

Testing

  • Full suite: 1137 passed.
  • Ruff clean on changed files.
  • DECO benchmark reproducible via make/download_corpora.sh (corpus is download-on-demand, gitignored).

🤖 Generated with Claude Code

arnav2 and others added 2 commits June 10, 2026 04:05
…o excel_parser

Renames the package ks_xlsx_parser → excel_parser (and rust ks_xlsx_core →
excel_core) across source, docs, scripts, and site, plus parser improvements:

- Header detection: extend find_header_span to multi-row header bands, gated on
  styling continuity so single-row headers stay one row. Measured on DECO (852
  files, 1,480 GT tables): multi-row header F1 0.37→0.50 (exact 0%→24%, recall
  0.23→0.33), single-row exact 84%→79%, table IoU unchanged. New unit tests in
  tests/test_header_detector.py.
- .xls support: convert_xls_to_xlsx backend so legacy workbooks parse.
- DECO structural benchmark (scripts/eval_deco.py): scores table-boundary IoU +
  header-row precision/recall/F1 vs Docling — the structural ground truth
  SpreadsheetBench lacks. Wired into download_corpora.sh + benchmarks README.

Full test suite: 1137 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuilt find_header_span around a scan/anchor/extend design driven by a
root-cause analysis of all 601 DECO failures (852 files, 1480 GT tables):

- keep scanning past non-header rows instead of aborting at the first
  multi-column row; titles/banners/key-value preambles/dividers/hidden
  rows no longer consume the scan window
- body-signature brake: a row that type-matches the stripe beneath it is
  data even when bold/filled — replaces blind trust in styling and lets
  unstyled (contrast-found) anchors extend safely, the dominant
  multi-row failure (57% of MR-UNDER)
- merge-aware: vertical merges hanging from the band join it; slaves
  inherit master type classes so merged body label columns brake the
  band; single wide horizontal merges are banners/section strips
- richer typing: ISO/slashed date strings, numeric strings, and Excel
  error literals count as data; year/date axis rows (2019|2020, month
  columns) are headers despite reading as data, with a >=2-axis-head
  run required so lone date values in key-value pairs don't qualify
- precision guards: strict-majority data rejection for anchors,
  column-coverage + key-value preamble demotion, repeating section-label
  stubs stay out, freeze-pane clip of over-extended bands, blank-gap
  anchors only replaced by contrast-strong ones
- perf: per-region memoization plus bounded scans (column profiles,
  body samples, merge expansion) — worst corpus file 105s -> 22s

DECO header cohorts (isolated, before -> after):
  single-row exact 79.5% -> 86.6%   multi-row exact 23.8% -> 45.1%
  multi-row F1 0.495 -> 0.650       all F1 0.628 -> 0.744
Table-boundary IoU untouched. Full suite 1148 passed; 17 new detector
tests. Also: chartsheet getattr guards in sheet_parser, cohort/diagnosis
tooling (scripts/analyze_deco_headers.py, scripts/diag_header_failure.py),
benchmark report under tests/benchmarks/reports/deco/header_band_v2/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@arnav2
arnav2 merged commit 4834382 into main Jul 16, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant