Skip to content

docs: add bench/ and docs/evaluations/, with the ICDAR 2013 result - #20

Merged
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/repo-structure-bench-docs
Aug 2, 2026
Merged

docs: add bench/ and docs/evaluations/, with the ICDAR 2013 result#20
hallelx2 merged 1 commit into
mainfrom
halleluyaholudele/repo-structure-bench-docs

Conversation

@hallelx2

@hallelx2 hallelx2 commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Benchmark harnesses and measurement findings had no home. The ICDAR 2013 harness was living in a temp directory, and every result from this work existed only in Linear — which records that something was decided, not what was measured.

What this adds

bench/                          separate Go module
  README.md
  icdar2013/
    run.py                      fetch dataset, build, score
    extract.go                  dumps pdftable's tables as JSON
    score.py                    adjacency-relation metric
    diag.py                     detection vs structure breakdown
docs/
  README.md
  evaluations/
    2026-08-02-icdar2013-table-structure.md
    2026-08-02-font-metrics-and-table-fidelity.md

bench/ is a separate Go module, deliberately

Nested modules are excluded from the parent's ./..., so the library build ignores it and a consumer of pdftable never fetches its dependencies.

The first version of this commit got that wronggo list ./... showed github.com/hallelx2/pdftable/bench/icdar2013 inside the library module, which is precisely the leak the split exists to prevent. Fixed and verified:

$ go list ./...
github.com/hallelx2/pdftable
github.com/hallelx2/pdftable/cmd/pdftable
github.com/hallelx2/pdftable/examples/extract_tables
github.com/hallelx2/pdftable/internal/layout
github.com/hallelx2/pdftable/internal/pdf

Datasets are not committed. run.py fetches ICDAR 2013 (~12 MB) into a cache dir, builds the extractor as its own module against the working tree, and scores pdftable alongside pdfplumber — "is 0.36 good?" is unanswerable without a baseline.

docs/evaluations/ — dated, append-only

Each report states the commit it ran against, the external oracle used, the metric precisely, what the number is not comparable to, and what remains untested.

That last part is the point. A benchmark number without its scope reads as a general claim. The 0.362 here is end-to-end detection plus structure — not the structure-only task that papers report 0.85–0.95 on, where the system is handed the table region.

Append-only means a regression appears as a diff between two dated files rather than vanishing into a rewrite.

What did NOT move, and why

The library source stays in the repository root. For a single-package Go library the root is the import path — moving page.go into src/ or pkg/ breaks every consumer, and pkg/ is a recognised Go anti-pattern. Go also deliberately keeps _test.go beside the code it covers.

The README now documents the layout, so the root is legible rather than merely conventional.

Verification

  • go build ./..., go vet ./..., go test ./... -count=1 -race — green.
  • bench/ confirmed absent from go list ./....
  • Harness re-run from its new home; results reproduce.

Relates to HAL-568

Summary by Sourcery

Add a standalone benchmarking module and documented evaluation results for font metrics and table extraction accuracy.

New Features:

  • Introduce a separate bench module with an ICDAR 2013 table benchmark harness comparing pdftable to pdfplumber.
  • Add dated evaluation reports for font metrics, table fidelity, and ICDAR 2013 end-to-end table detection and structure accuracy.

Enhancements:

  • Document the repository layout and link roadmap items to their corresponding evaluation reports in the main README.
  • Clarify benchmark conventions and reporting structure under a new docs/README with append-only evaluations directory.

Summary by CodeRabbit

  • Documentation

    • Updated the roadmap with completed font-metric and encoding improvements, measured position fidelity, and upcoming table-detection benchmarks.
    • Added documentation indexes, repository layout guidance, benchmark usage instructions, dataset licensing details, and evaluation-report conventions.
    • Published evaluation reports covering font fidelity and ICDAR 2013 table-structure results, including limitations and diagnostic findings.
  • New Features

    • Added an ICDAR 2013 benchmark workflow with table extraction, scoring, system comparisons, and diagnostic reporting.
    • Added support for downloading, caching, and evaluating the benchmark dataset.

Benchmark harnesses and measurement findings had no home. The ICDAR 2013
harness was living in a temp directory and every result from this work
existed only in the issue tracker, where it records that something was
decided rather than what was measured.

bench/ is a SEPARATE Go module. Nested modules are excluded from the
parent ./... , so the library build ignores it and a consumer of pdftable
never fetches its dependencies. The first version of this commit did not
do that and go list ./... showed bench/icdar2013 inside the library
module -- exactly the leak the split exists to prevent.

Datasets are not committed. run.py fetches ICDAR 2013 into a cache
directory, builds the extractor as its own module against the working
tree, and scores pdftable alongside pdfplumber -- "is 0.36 good?" is
unanswerable without a baseline.

docs/evaluations/ holds dated reports. Each states the commit it ran
against, the external oracle used, the metric precisely, what the number
is NOT comparable to, and what remains untested. That last part is the
point: a benchmark number without its scope reads as a general claim, and
0.36 here is end-to-end detection plus structure, not the structure-only
task papers report 0.85-0.95 on.

Reports are append-only, so a regression shows up as a diff between two
dated files rather than disappearing into a rewrite.

The library source stays in the repository root. That is the import path
for a single-package Go library, and moving it would break every
consumer; Go also deliberately keeps _test.go files beside the code they
cover. The README now documents the layout so the root is legible rather
than merely conventional.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @hallelx2, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds an isolated ICDAR 2013 benchmark module with extraction, scoring, diagnostics, and dataset setup. It also adds benchmark documentation, evaluation reports, roadmap updates, and repository layout documentation.

Changes

ICDAR 2013 Benchmark

Layer / File(s) Summary
Adjacency scoring engine
bench/icdar2013/score.py
Parses ground-truth XML, derives adjacency relations, scores extraction results, and compares pdftable with pdfplumber.
Extractor and benchmark execution
bench/go.mod, bench/icdar2013/extract.go, bench/icdar2013/run.py
Builds an isolated Go extractor, retrieves the dataset, runs extraction strategies, and invokes scoring with optional limits and diagnostics.
Benchmark diagnostics
bench/icdar2013/diag.py
Reports detection failures, relation scores, zero-detection rates, and documents with the most missed relations.
Benchmark and evaluation records
bench/README.md, bench/icdar2013/README.md, docs/README.md, docs/evaluations/*, README.md
Documents benchmark operation, metric scope, evaluation findings, roadmap status, and repository structure.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Runner as run.py
  participant Dataset as ICDAR 2013 dataset
  participant Extractor as extract.go
  participant Scorer as score.py
  Runner->>Dataset: download or reuse dataset
  Runner->>Extractor: build isolated extractor
  Runner->>Scorer: run benchmark
  Scorer->>Extractor: extract tables from PDFs
  Extractor-->>Scorer: return JSON table rows
  Scorer-->>Runner: report precision, recall, and F1
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: adding the benchmark module, evaluation reports, and ICDAR 2013 results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch halleluyaholudele/repo-structure-bench-docs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new benchmarks module and documented evaluation reports for ICDAR 2013 table-structure accuracy and font/table fidelity, including a Python+Go harness that scores pdftable and pdfplumber on the ICDAR 2013 dataset, and updates the README to describe the repository layout and link to these evaluations.

Sequence diagram for the ICDAR 2013 benchmark harness

sequenceDiagram
    actor User
    participant run_py as run_py
    participant extract_go as extract_go_exe
    participant score_py as score_py
    participant pdfplumber as pdfplumber
    participant Dataset as icdar2013_dataset

    User->>run_py: main()
    run_py->>run_py: scratch()
    run_py->>Dataset: fetch(dest)
    run_py->>extract_go: build_extractor(dest)
    run_py->>score_py: subprocess.run(score_py, root, exe)
    score_py->>Dataset: gt_relations(xml_path)
    score_py->>extract_go: run_pdftable(exe, pdf, strategy, merge)
    score_py->>pdfplumber: run_pdfplumber(pdf, strategy)
    score_py->>User: print precision/recall/F1 table
Loading

File-Level Changes

Change Details Files
Introduce a separate bench Go module with an ICDAR 2013 benchmark harness that runs pdftable and pdfplumber against the corrected ICDAR 2013 dataset and reports precision/recall/F1, without leaking dependencies into the main library module.
  • Create bench/go.mod to define a standalone Go module that depends on github.com/hallelx2/pdftable via a local replace, ensuring nested modules are excluded from the root go list ./....
  • Add bench/icdar2013/extract.go, a small CLI that runs pdftable with configurable table strategies, extracts all tables per page, and emits them as JSON for scoring.
  • Add bench/icdar2013/run.py to download and cache the corrected ICDAR 2013 dataset, build the Go extractor inside an isolated module, and orchestrate scoring and optional diagnostics via score.py and diag.py.
  • Add bench/icdar2013/score.py implementing the adjacency-relations metric (per ICDAR 2013) over ground-truth XML and detected tables, accumulating precision/recall/F1 across documents and comparing pdftable strategies vs pdfplumber.
  • Add bench/icdar2013/diag.py to distinguish detection failures from structure failures by analysing per-document relations and recomputing metrics restricted to documents where at least one table was detected.
  • Document the ICDAR 2013 harness in bench/icdar2013/README.md, including usage, metric rationale, and interpretation of results.
  • Add bench/README.md to describe the role of accuracy benchmarks, their separation from go test benchmarks, dataset-handling conventions, and how to add new benchmarks.
bench/go.mod
bench/icdar2013/extract.go
bench/icdar2013/run.py
bench/icdar2013/score.py
bench/icdar2013/diag.py
bench/icdar2013/README.md
bench/README.md
Add structured documentation for evaluations, including detailed reports for font metrics/table fidelity and ICDAR 2013 table-structure results, and cross-link them from docs and the root README.
  • Create docs/README.md to describe the documentation area, especially docs/evaluations/, and define conventions for dated, append-only evaluation reports with explicit metrics, datasets, and untested scopes.
  • Add docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md, documenting the audit of font metrics, glyph boxes, negative-sign handling, and word segmentation, including headline before/after metrics and methodological notes.
  • Add docs/evaluations/2026-08-02-icdar2013-table-structure.md, documenting the ICDAR 2013 end-to-end benchmark setup, headline results vs pdfplumber, interpretation that recall is detection-limited, and implications for future work.
  • Update the root README.md to link to the new evaluation reports for v0.4.x and v0.5.x milestones, summarise the measured outcomes, and adjust roadmap text accordingly.
  • Update README.md with a new Repository layout section that explains where each major directory lives (including bench/ and docs/evaluations/) and why the library source remains at the repo root with tests colocated.
docs/README.md
docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md
docs/evaluations/2026-08-02-icdar2013-table-structure.md
README.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bench/icdar2013/diag.py`:
- Around line 59-63: Update the reporting block in the diagnostic script to
handle an empty `pairs` collection before calculating the zero-detection
percentage. Report a clear “no documents found” message when no pairs exist, and
preserve the existing percentage output for non-empty collections.
- Around line 43-57: Update the subprocess and JSON parsing flow in the
diagnostic loop to catch timeout, execution, and malformed-output errors,
matching the fallback behavior of run_pdftable in score.py. Treat any failed or
invalid PDF result as empty tables, then continue scoring and appending results
for the remaining documents.

In `@bench/icdar2013/README.md`:
- Line 8: Update the ICDAR2013 benchmark installation instruction to pin
pdfplumber to version 0.11.9, ensuring future runs use the same dependency
version as the reference oracle.

In `@bench/icdar2013/run.py`:
- Line 55: Configure a module-level network timeout before the dataset download
in run.py by importing socket and setting socket.setdefaulttimeout(60). Keep the
existing urllib.request.urlretrieve(URL, tgz) call unchanged while ensuring
stalled downloads fail after the specified timeout.
- Around line 57-58: Update the tarfile extraction in the download/archive
handling block to pass tarfile.data_filter to extractall, ensuring entries
remain confined to dest while preserving the existing extraction behavior.

In `@bench/icdar2013/score.py`:
- Around line 104-134: Update the exception handlers in run_pdftable and
run_pdfplumber to write extraction failures and their error details to stderr
before returning Counter(). Preserve the existing empty-counter fallback, and
use the extract.go-style diagnostic pattern so missing executables, subprocess
failures, and pdfplumber errors are visible.
- Around line 38-59: Update relations_from_grid to accept or derive source-cell
IDs alongside normalized grid text, preserving each covered position’s
originating cell identity. When selecting the nearest non-empty horizontal or
vertical neighbor, skip candidates whose source ID matches the current cell’s
ID, while retaining relations between distinct cells and counting each position
according to the existing traversal.

In `@docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md`:
- Around line 3-5: Update the evaluation metadata near the Commits entry to
explicitly identify the exact commit tree used for the reported measurements,
while retaining the existing commit range only as historical context.
- Around line 103-108: Update the “Findings were confirmed” statement to avoid
describing pdftoppm and pdftotext as independent sources; either label the three
items as verification signals or add a renderer/parser from a non-Poppler
implementation before retaining the claim of three independent sources.

In `@docs/evaluations/2026-08-02-icdar2013-table-structure.md`:
- Around line 30-37: Update the diagnostic output fenced block in the evaluation
report to declare the text language, preserving its existing contents and
formatting so it satisfies Markdown lint rule MD040.
- Around line 89-90: Add a “What remains untested” section in the evaluation
report before the closing roadmap note, documenting scope limitations including
scanned PDFs and document or layout classes not represented in the 125
born-digital PDFs.

In `@docs/README.md`:
- Around line 31-33: Update the report filename convention described in the
README to include a collision-free timestamp or sequence suffix after the date
and subject, ensuring same-day benchmark reruns create distinct append-only
files while preserving the existing dated-report behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bdf11d17-72a3-47d0-b628-6c91f3a73fad

📥 Commits

Reviewing files that changed from the base of the PR and between 0ca65ca and 21899e0.

📒 Files selected for processing (11)
  • README.md
  • bench/README.md
  • bench/go.mod
  • bench/icdar2013/README.md
  • bench/icdar2013/diag.py
  • bench/icdar2013/extract.go
  • bench/icdar2013/run.py
  • bench/icdar2013/score.py
  • docs/README.md
  • docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md
  • docs/evaluations/2026-08-02-icdar2013-table-structure.md

Comment thread bench/icdar2013/diag.py
Comment on lines +43 to +57
for pdf, xml in pairs:
gt = gt_relations(xml)
out = subprocess.run(
[exe, "-strategy", "lines", pdf], capture_output=True, timeout=120
).stdout
tables = json.loads(out or b"[]")
detected_tables_total += len(tables)
rels: Counter = Counter()
for t in tables:
rels += relations_from_grid([[norm(c) for c in r] for r in t["rows"]])
c, nd, ng = score(gt, rels)
if nd == 0:
zero_detect += 1
gt_regions_total += 1
per_doc.append((os.path.basename(pdf), ng, nd, c, len(tables)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

One slow or failing PDF aborts the entire diagnostic run.

Unlike run_pdftable in score.py (which wraps the equivalent subprocess call in try/except and falls back to an empty result), the subprocess call and json.loads here have no exception handling. A single PDF that exceeds the 120s timeout, or produces malformed stdout, raises an uncaught exception and stops the diagnostic before it reports results for the rest of the dataset.

🔧 Proposed fix
     for pdf, xml in pairs:
         gt = gt_relations(xml)
-        out = subprocess.run(
-            [exe, "-strategy", "lines", pdf], capture_output=True, timeout=120
-        ).stdout
-        tables = json.loads(out or b"[]")
+        try:
+            out = subprocess.run(
+                [exe, "-strategy", "lines", pdf], capture_output=True, timeout=120
+            ).stdout
+            tables = json.loads(out or b"[]")
+        except Exception as e:
+            print(f"warning: extraction failed for {pdf}: {e}", file=sys.stderr)
+            tables = []
         detected_tables_total += len(tables)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for pdf, xml in pairs:
gt = gt_relations(xml)
out = subprocess.run(
[exe, "-strategy", "lines", pdf], capture_output=True, timeout=120
).stdout
tables = json.loads(out or b"[]")
detected_tables_total += len(tables)
rels: Counter = Counter()
for t in tables:
rels += relations_from_grid([[norm(c) for c in r] for r in t["rows"]])
c, nd, ng = score(gt, rels)
if nd == 0:
zero_detect += 1
gt_regions_total += 1
per_doc.append((os.path.basename(pdf), ng, nd, c, len(tables)))
for pdf, xml in pairs:
gt = gt_relations(xml)
try:
out = subprocess.run(
[exe, "-strategy", "lines", pdf], capture_output=True, timeout=120
).stdout
tables = json.loads(out or b"[]")
except Exception as e:
print(f"warning: extraction failed for {pdf}: {e}", file=sys.stderr)
tables = []
detected_tables_total += len(tables)
rels: Counter = Counter()
for t in tables:
rels += relations_from_grid([[norm(c) for c in r] for r in t["rows"]])
c, nd, ng = score(gt, rels)
if nd == 0:
zero_detect += 1
gt_regions_total += 1
per_doc.append((os.path.basename(pdf), ng, nd, c, len(tables)))
🧰 Tools
🪛 ast-grep (0.45.0)

[error] 44-46: Command coming from incoming request
Context: subprocess.run(
[exe, "-strategy", "lines", pdf], capture_output=True, timeout=120
)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.16.0)

[error] 45-45: subprocess call: check for execution of untrusted input

(S603)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/diag.py` around lines 43 - 57, Update the subprocess and JSON
parsing flow in the diagnostic loop to catch timeout, execution, and
malformed-output errors, matching the fallback behavior of run_pdftable in
score.py. Treat any failed or invalid PDF result as empty tables, then continue
scoring and appending results for the remaining documents.

Comment thread bench/icdar2013/diag.py
Comment on lines +59 to +63
print(f"documents : {len(pairs)}")
print(f"documents where we found NO table: {zero_detect}"
f" ({100*zero_detect/len(pairs):.0f}%)")
print(f"tables detected (total) : {detected_tables_total}")
print()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Guard against division by zero when no documents are found.

If root contains no matching -str.xml/.pdf pairs, pairs is empty and 100*zero_detect/len(pairs) at Line 61 raises ZeroDivisionError instead of reporting a clear "no documents found" message.

🛡️ Proposed fix
     pairs.sort()
 
+    if not pairs:
+        print(f"no documents found under {root}")
+        return 1
+
     zero_detect = 0
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/diag.py` around lines 59 - 63, Update the reporting block in
the diagnostic script to handle an empty `pairs` collection before calculating
the zero-detection percentage. Report a clear “no documents found” message when
no pairs exist, and preserve the existing percentage output for non-empty
collections.

Comment thread bench/icdar2013/README.md
ground truth.

```sh
pip install pdfplumber

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)bench/icdar2013/README\.md$|pdfplumber|requirements|lock' || true

echo
echo "README excerpt:"
if [ -f bench/icdar2013/README.md ]; then
  nl -ba bench/icdar2013/README.md | sed -n '1,80p'
fi

echo
echo "Occurrences:"
rg -n "pdfplumber 0\.11\.9|pip install pdfplumber|0\.11\.9" .

Repository: hallelx2/pdftable

Length of output: 288


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "README lines:"
awk '{printf "%4d\t%s\n", NR, $0}' bench/icdar2013/README.md | sed -n '1,90p'

echo
echo "Script excerpt and pdfplumber references:"
awk '{printf "%4d\t%s\n", NR, $0}' scripts/capture_pdfplumber_text_golden.py | sed -n '1,160p'
rg -n "pdfplumber|0\.11\.9|pip install pdfplumber|pip freeze|requirements|lock" .

Repository: hallelx2/pdftable

Length of output: 39482


Pin pdfplumber for the ICDAR2013 benchmark.

The ICDAR2013 evaluation reports the reference oracle as pdfplumber 0.11.9, but pip install pdfplumber can install another version. Pin the dependency in the README/run instructions or document a lockfile so later runs remain comparable.

Proposed fix
-pip install pdfplumber
+pip install pdfplumber==0.11.9
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pip install pdfplumber
pip install pdfplumber==0.11.9
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/README.md` at line 8, Update the ICDAR2013 benchmark
installation instruction to pin pdfplumber to version 0.11.9, ensuring future
runs use the same dependency version as the reference oracle.

Comment thread bench/icdar2013/run.py
tgz = os.path.join(dest, "icdar2013.tar.gz")
if not os.path.exists(tgz):
print(f"downloading {URL}")
urllib.request.urlretrieve(URL, tgz)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a timeout to the dataset download.

urllib.request.urlretrieve(URL, tgz) has no timeout. A network stall hangs the harness indefinitely instead of failing with a clear error.

⏱️ Proposed fix
-        urllib.request.urlretrieve(URL, tgz)
+        urllib.request.urlretrieve(URL, tgz, reporthook=None)

Set a module-level timeout instead, since urlretrieve has no timeout parameter:

import socket
socket.setdefaulttimeout(60)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/run.py` at line 55, Configure a module-level network timeout
before the dataset download in run.py by importing socket and setting
socket.setdefaulttimeout(60). Keep the existing urllib.request.urlretrieve(URL,
tgz) call unchanged while ensuring stalled downloads fail after the specified
timeout.

Comment thread bench/icdar2013/run.py
Comment on lines +57 to +58
with tarfile.open(tgz) as t:
t.extractall(dest)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file excerpt =="
if [ -f bench/icdar2013/run.py ]; then
  nl -ba bench/icdar2013/run.py | sed -n '1,120p'
else
  echo "bench/icdar2013/run.py not found"
fi

echo
echo "== setup files =="
git ls-files | rg '(^|/(setup\.py|pyproject\.toml|requirements.*\.txt|.*setup.*\.cfg|README.*))$' | sed -n '1,80p'

echo
echo "== python version declarations =="
for f in $(git ls-files | rg '(^|/)(pyproject\.toml|setup\.py|setup\.cfg|requirements.*\.txt|README.*|.*requirements.*\.txt)$'); do
  echo "--- $f ---"
  rg -n 'python_requires|Requires-Python|Programming Language :: Python|python_version|python-full-version|Python\s*[0-9.]*' "$f" || true
done

echo
echo "== tarfile extractall occurrences =="
rg -n 'extractall\(|data_filter|tarfile\.open' .

Repository: hallelx2/pdftable

Length of output: 221


🌐 Web query:

Python tarfile extractall filter data CVE-2007-4559 default filter None documentation

💡 Result:

CVE-2007-4559 is a directory traversal vulnerability in Python's tarfile module, where maliciously crafted tar archives containing ".." (dot-dot) sequences or symbolic links could allow an attacker to overwrite arbitrary files outside the intended destination directory [1][2]. Historically, the Python tarfile.extractall method did not implement any security checks, and official documentation warned users against extracting archives from untrusted sources without manual inspection [3][4]. To address this, Python introduced the filter argument in PEP 706 [3]. Key details regarding the filter, its defaults, and CVE-2007-4559 include: 1. Evolution of the Default Filter: - Historically (before Python 3.14), the default behavior when no filter was provided was effectively 'fully_trusted', which performed no security checks and left applications vulnerable to CVE-2007-4559 [3][4]. - Starting in Python 3.14, the default filter for extractall and extract was changed to 'data', which blocks dangerous features like absolute paths or links to files outside the destination directory [4]. - You can manually set a default behavior by configuring the TarFile.extraction_filter attribute [4]. If this is set to None (the default), the library will use the 'data' filter (in Python 3.14+) [4]. 2. Available Filters: - 'data': Intended for cross-platform data archives; it blocks or ignores dangerous features like UNIX-specific metadata or links pointing outside the extraction path [4][5]. - 'tar': Honors most UNIX-like filesystem features but blocks features deemed surprising or malicious [4][5]. - 'fully_trusted': Explicitly trusts all metadata in the archive. This should only be used if the archive is fully trusted or if the user performs their own complex validation [3][4]. 3. Newer Vulnerabilities: While the 'data' filter was designed to mitigate CVE-2007-4559, more recent vulnerabilities (e.g., CVE-2025-4517, CVE-2025-4330) have been discovered that affect the 'data' and 'tar' filters in Python 3.12 and later [6][7]. These issues allow for arbitrary filesystem writes or symlink attacks even when using these filters [6][7]. Developers should ensure their Python environment is updated to include patches for these more recent vulnerabilities, as the standard 'data' filter alone may not be sufficient on unpatched systems [6][7]. For maximum security when dealing with untrusted archives, it remains recommended to avoid extracting archives from untrusted sources whenever possible [4][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== target file excerpt =="
if [ -f bench/icdar2013/run.py ]; then
  awk '{printf "%6d  %s\n", NR, $0}' bench/icdar2013/run.py | sed -n '1,120p'
else
  echo "bench/icdar2013/run.py not found"
fi

echo
echo "== repository files of interest =="
git ls-files | grep -E '(^|/)(pyproject\.toml|setup\.py|setup\.cfg|requirements.*\.txt|README.*|\.python-version|Dockerfile|.*requirements.*\.txt)$' | sed -n '1,120p'

echo
echo "== python version declarations =="
for f in $(git ls-files | grep -E '(^|/)(pyproject\.toml|setup\.py|setup\.cfg|requirements.*\.txt|README.*|\.python-version|Dockerfile|.*requirements.*\.txt)$'); do
  echo "--- $f ---"
  grep -n -E 'python_requires|Requires-Python|Programming Language :: Python|python_version|python-full-version|Python\s*[0-9.]*' "$f" || true
done

echo
echo "== tarfile extractall occurrences =="
grep -rn 'extractall\(|data_filter|tarfile\.open' . || true

echo
echo "== deterministic tarfile behavior probe =="
python3 - <<'PY'
import sys
print(f"python={sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
import tarfile
if hasattr(tarfile.TarFile, 'extractall'):
    import inspect
    sig = inspect.signature(tarfile.TarFile.extractall)
    print("extractall signature:", sig)
    print("has extraction_filter:", hasattr(tarfile.TarFile, 'extraction_filter'))
if hasattr(tarfile, 'data_filter'):
    print("has tarfile.data_filter")
PY

Repository: hallelx2/pdftable

Length of output: 5686


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tarfile occurrences =="
grep -RIn 'extractall\(|data_filter|tarfile\.open' . || true

echo
echo "== inspect repository Python references without rg/fd dependencies as needed =="
for f in README.md bench/README.md bench/icdar2013/README.md docs/README.md; do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  grep -In -E 'Python|python|tarfile|extractall|requirements|version|data_filter' "$f" || true
done

Repository: hallelx2/pdftable

Length of output: 1246


Pass a filter to tarfile.extractall().

bench/icdar2013/run.py:58 extracts a downloaded tarball without filter=. An archive entry with a ../ path can extract outside dest; use tarfile.data_filter here to keep extraction confined.

🧰 Tools
🪛 ast-grep (0.45.0)

[error] 57-57: Calling extractall() on a zipfile.ZipFile or tarfile archive without validating member paths lets a crafted entry (e.g. "../../etc/passwd") write outside the destination directory (Zip Slip). Validate each member resolves inside the target directory, or pass a safe filter (tarfile: filter="data" / tarfile.data_filter).
Context: t.extractall(dest)
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(archive-extractall-path-traversal-python)

🪛 Ruff (0.16.0)

[error] 58-58: Uses of tarfile.extractall()

(S202)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bench/icdar2013/run.py` around lines 57 - 58, Update the tarfile extraction
in the download/archive handling block to pass tarfile.data_filter to
extractall, ensuring entries remain confined to dest while preserving the
existing extraction behavior.

Comment on lines +3 to +5
**Date:** 2026-08-02
**Commits:** `325e628` … `0ca65ca` (PRs #9, #13–#19)
**References:** rendered pages via `pdftoppm`, poppler `pdftotext -layout`, pdfplumber 0.11.9

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Record the exact commit used for the measurements.

Commits: 325e628 … 0ca65ca describes a range, but it does not identify which tree produced the reported values. Record the measured commit explicitly and keep the range only as historical context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md` around lines
3 - 5, Update the evaluation metadata near the Commits entry to explicitly
identify the exact commit tree used for the reported measurements, while
retaining the existing commit range only as historical context.

Comment on lines +103 to +108
Findings were confirmed against three independent sources before being
accepted:

1. the page rendered to PNG (`pdftoppm -r 150`) and read directly,
2. poppler `pdftotext -layout`,
3. pdfplumber 0.11.9.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching target name:"
fd -a '2026-08-02-font-metrics-and-table-fidelity\.md' . || true

target="$(fd '2026-08-02-font-metrics-and-table-fidelity\.md' . | head -n 1 || true)"
if [ -n "${target:-}" ]; then
  echo
  echo "Relevant lines:"
  sed -n '90,115p' "$target" | nl -ba -v90
fi

echo
echo "Search for independent sources wording in repo:"
rg -n "independent sources|independent source|verification signals|pdftoppm|pdftotext|pdfplumber" . || true

Repository: hallelx2/pdftable

Length of output: 323


🏁 Script executed:

#!/bin/bash
set -u

target="/home/jailuser/git/docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md"
echo "Relevant lines:"
awk '{ printf "%5d  %s\n", NR, $0 }' "$target" | sed -n '90,115p'

echo
echo "Search for independent sources wording in repo:"
rg -n "independent sources|independent source|verification signals|pdftoppm|pdftotext|pdfplumber" /home/jailuser/git || true

Repository: hallelx2/pdftable

Length of output: 40179


Do not count two Poppler outputs as independent sources.

pdftoppm and pdftotext are outputs from the Poppler toolchain. Describe them as verification signals, or add a PDF renderer/parser from another implementation before claiming three independent sources.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/evaluations/2026-08-02-font-metrics-and-table-fidelity.md` around lines
103 - 108, Update the “Findings were confirmed” statement to avoid describing
pdftoppm and pdftotext as independent sources; either label the three items as
verification signals or add a renderer/parser from a non-Poppler implementation
before retaining the claim of three independent sources.

Comment on lines +30 to +37
```
documents : 125
documents where NO table was found : 28 (22%)
tables detected (total) : 306

restricted to documents where a table WAS detected:
precision 0.865 recall 0.409 F1 0.556
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Specify a language for the diagnostic output block.

Add text to the fence so the report satisfies Markdown lint rule MD040.

🧰 Tools
🪛 markdownlint-cli2 (0.23.1)

[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/evaluations/2026-08-02-icdar2013-table-structure.md` around lines 30 -
37, Update the diagnostic output fenced block in the evaluation report to
declare the text language, preserving its existing contents and formatting so it
satisfies Markdown lint rule MD040.

Source: Linters/SAST tools

Comment on lines +89 to +90
Tracked as HAL-568. Re-run this harness after each and record a new dated
report here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required untested-scope section.

docs/README.md states that every evaluation report must document what remains untested. Add a ## What remains untested section before the closing roadmap note. Include scope limits such as scanned PDFs and document or layout classes absent from the 125 born-digital PDFs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/evaluations/2026-08-02-icdar2013-table-structure.md` around lines 89 -
90, Add a “What remains untested” section in the evaluation report before the
closing roadmap note, documenting scope limitations including scanned PDFs and
document or layout classes not represented in the 125 born-digital PDFs.

Comment thread docs/README.md
Comment on lines +31 to +33
Reports are append-only. Re-running a benchmark adds a new dated file
rather than editing an old one, so a regression is visible as a diff
between two reports instead of vanishing into a rewrite.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use collision-free filenames for append-only reports.

The current filenames use YYYY-MM-DD-<subject>.md. Two runs of the same benchmark on the same date would target the same path, which conflicts with the append-only requirement.

Define a timestamp or sequence suffix for same-day reruns.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/README.md` around lines 31 - 33, Update the report filename convention
described in the README to include a collision-free timestamp or sequence suffix
after the date and subject, ensuring same-day benchmark reruns create distinct
append-only files while preserving the existing dated-report behavior.

@hallelx2
hallelx2 merged commit 05c0c92 into main Aug 2, 2026
5 checks passed
@hallelx2
hallelx2 deleted the halleluyaholudele/repo-structure-bench-docs branch August 2, 2026 22:15
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