Compare commits

..
1 Commits
Author SHA1 Message Date
Abimael MartellandClaude Opus 4.6 780efdb955 extract_tables_in_regions: detect paragraph-as-table misreads (0.4.2)
Publish npm package / Build x86_64-unknown-linux-gnu (push) Has been cancelled
Publish npm package / Publish to npm (push) Has been cancelled
Publish npm package / Build aarch64-apple-darwin (push) Has been cancelled
Adds a 5th failure-mode check to looks_like_partial_table: when the
heuristic mis-detects text-wrapped paragraph prose as a multi-column
table, cells in the same column tend to start with lowercase letters
or continuation punctuation (commas, closing quotes) — because they're
actually sentence fragments. Real tables almost never have most data
cells starting lowercase.

Trigger: ≥2 cols, ≥4 data rows, ≥60% of non-empty data cells start
with lowercase or continuation punctuation → return needs_ocr=true.

Caught in the eval as the next-largest failure mode after the 0.4.1 fix:
PDFs 088, 182, 090 — heuristic produced "tables" like:

  |Approval is needed from the|Acquisitions of|
  |Treasurer if the acquisition|residential and|
  |constitutes a "significant|agricultural|
  |action," including acquiring an|land by foreign|

Reading column 1 top-to-bottom: "Approval is needed from the Treasurer
if the acquisition constitutes a 'significant action,' including
acquiring an interest..." — a paragraph, not tabular data.

Tests: 2 new tests (the 088-style failure case + a real multi-word
table that must NOT be flagged). All 11 looks_like_partial_table tests
pass; 323 unit + 91 integration tests still green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 15:09:08 -07:00
2 changed files with 79 additions and 1 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "firecrawl-pdf-inspector",
"version": "0.4.1",
"version": "0.4.2",
"description": "Fast PDF classification and text extraction. Detect text-based vs scanned PDFs, extract text by region with quality checks. Native Rust performance via napi-rs.",
"main": "index.js",
"types": "index.d.ts",
+78
View File
@@ -1298,6 +1298,56 @@ fn looks_like_partial_table(markdown: &str) -> bool {
}
}
// Failure mode 5: cells flow as continuation paragraph (text wrapping
// mistaken for column structure). When a paragraph of prose gets mis-
// detected as a multi-column table, cells in the same column tend to
// start with lowercase letters or punctuation (continuation), not
// capital letters / digits (new entries). Real tables almost never
// have most data cells starting lowercase.
//
// Signal: ≥2 cols, ≥4 data rows, and ≥60% of non-empty data cells
// start with a lowercase letter or continuation punctuation.
let data_rows: Vec<Vec<&str>> = lines
.iter()
.skip(2) // header + separator
.map(|l| {
let parts: Vec<&str> = l.split('|').map(|s| s.trim()).collect();
if parts.len() >= 3 {
parts[1..parts.len() - 1].to_vec()
} else {
Vec::new()
}
})
.filter(|cells| !cells.is_empty())
.collect();
if n_cols >= 2 && data_rows.len() >= 4 {
let mut continuation = 0;
let mut total = 0;
for row in &data_rows {
for cell in row {
let trimmed = cell.trim();
if trimmed.is_empty() {
continue;
}
total += 1;
let first = trimmed.chars().next().unwrap();
// Continuation indicators: lowercase letter, common
// mid-sentence punctuation, closing quote
if first.is_lowercase()
|| matches!(first, ',' | '.' | ';' | ')' | '"' | '\'' | '”' | '')
{
continuation += 1;
}
}
}
if total > 0 && continuation * 5 >= total * 3 {
// ≥60% of cells look like sentence continuations → paragraph
// misread as table.
return true;
}
}
false
}
@@ -1377,6 +1427,34 @@ mod looks_like_partial_table_tests {
let md = "|A|B|C|D|\n|---|---|---|---|\n|x|y||z|\n|p|q|r|s|";
assert!(!looks_like_partial_table(md));
}
#[test]
fn paragraph_misread_as_two_column_table_is_partial() {
// Real production failure: text-wrapped paragraph mis-detected as
// 2-col table. Each cell continues the previous one as prose.
let md = "|Approval is needed from the|Acquisitions of|\n\
|---|---|\n\
|Treasurer if the acquisition|residential and|\n\
|constitutes a \"significant|agricultural|\n\
|action,\" including acquiring an|land by foreign|\n\
|interest in different types of|persons must be|\n\
|land where the monetary|reported to the|";
assert!(looks_like_partial_table(md));
}
#[test]
fn real_multi_word_table_is_kept() {
// Real table with multi-word entries — cells start with capital
// letters / proper nouns, NOT lowercase continuations.
let md = "|Country|Capital|Notes|\n\
|---|---|---|\n\
|United States|Washington DC|Federal capital|\n\
|United Kingdom|London|City of London is a separate|\n\
|France|Paris|Île-de-France region|\n\
|Germany|Berlin|Reunified 1990|\n\
|Spain|Madrid|Largest city in Spain|";
assert!(!looks_like_partial_table(md));
}
}
/// Analyse extracted items and rects for layout complexity.