fix: improve table detection for numeric columns and multi-line headers (#35)
* fix: improve heuristic table detection for numeric columns and multi-line headers Two fixes for tables that have clean extractable text but fail heuristic structure detection: 1. Numeric column merge pass (grid.rs): After initial X-position clustering, adjacent clusters are merged when one is sparse (header text) and the other is dense with >50% numeric items (data column). Multi-line wrapped headers often land slightly offset from their data column — the merge closes gaps within 1.5× the clustering threshold. New is_numeric_text() helper matches decimals, percentages, negative numbers, and comma-separated thousands. 2. Duplicate-header skip (detect_heuristic.rs): Spanning super-headers like "First Degree | First Degree | Higher Degree" contain duplicate cells that trigger looks_like_partial_table_ex rejection. Now skips rows with duplicate cells when a better header candidate exists within the next 3 rows (higher fill ratio or numeric cells). Tested on BITS Pilani university report (430 pages, 314 table pages). Page 4 (multi-line header + numeric data) previously returned needs_ocr=true; now correctly detects the table structure. Eval: 197 PDFs, zero regressions, all 104+ tests pass, zero clippy. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * bump version to 0.7.1 --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
35445c3208
commit
7c8b09be67
BIN
Binary file not shown.
@@ -1438,6 +1438,42 @@ fn test_extract_tables_in_regions_nonexistent_page() {
|
||||
assert!(region.text.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bits_pilani_page4_table_detection() {
|
||||
// Page 4 (0-indexed 3) has a table with multi-line wrapped headers and
|
||||
// numeric data columns. The heuristic detector previously failed because:
|
||||
// 1. Header items at different X positions than data created extra column
|
||||
// clusters (6 cols instead of 4)
|
||||
// 2. Spanning super-header row ("First Degree | First Degree") produced
|
||||
// duplicate header cells that looks_like_partial_table_ex rejected
|
||||
let buf = std::fs::read("tests/fixtures/bits_pilani_feedback.pdf").unwrap();
|
||||
let results =
|
||||
extract_tables_in_regions_mem(&buf, &[(3, vec![[0.0, 0.0, 612.0, 792.0]])]).unwrap();
|
||||
assert_eq!(results.len(), 1);
|
||||
let region = &results[0].regions[0];
|
||||
assert!(
|
||||
!region.needs_ocr,
|
||||
"Page 4 table should be detected, got needs_ocr=true"
|
||||
);
|
||||
assert!(
|
||||
region.text.contains("BIO"),
|
||||
"Should contain department name BIO"
|
||||
);
|
||||
assert!(region.text.contains("8.23"), "Should contain numeric data");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bits_pilani_page8_table_detection() {
|
||||
// Page 8 (0-indexed 7) has a numbered-row table that already worked.
|
||||
// Verify it still works after changes.
|
||||
let buf = std::fs::read("tests/fixtures/bits_pilani_feedback.pdf").unwrap();
|
||||
let results =
|
||||
extract_tables_in_regions_mem(&buf, &[(7, vec![[0.0, 0.0, 612.0, 792.0]])]).unwrap();
|
||||
assert_eq!(results.len(), 1);
|
||||
let region = &results[0].regions[0];
|
||||
assert!(!region.needs_ocr, "Page 8 table should still be detected");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// extract_pages_markdown_mem tests
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user