Commit graph

119 commits

Author SHA1 Message Date
LLLin000
2216ac7d87 fix(pr3): remove same-column guard from body_blocks, fix _page_width_for_zone_block arg 2026-07-04 22:36:46 +08:00
LLLin000
53a5336b6a feat(pr3): add column-aware same-page reference boundary 2026-07-04 22:36:46 +08:00
LLLin000
7bf652c42f feat: Workstream B — OCR tail settlement extraction and report
- B0: extract tail/backmatter settlement helpers into ocr_tail_settlement.py
- B1: add TailSettlementReport dataclass attached to DocumentStructure
- B2 gate: 41/41 tests pass (3 B0/B1 + 6 tail regressions + 32 A regression)
2026-07-04 13:37:45 +08:00
LLLin000
239d44bfe4 feat(ocr): checkpoint layout robustness and figure vnext groundwork on master 2026-07-03 17:48:50 +08:00
LLLin000
41e35239f2 fix: inline <table> HTML blocks incorrectly assigned to media_asset
Three-part fix:
1. ocr_roles.py: add inline <table> check before raw_label=table → media_asset
   fallback. Blocks starting with <table> now get table_html directly regardless
   of raw_label.
2. ocr_structural_gate.py: add table_html verifier — inline <table> HTML is
   self-identifying, accepted without structural verification.
3. ocr_document.py: remove table_html→table_html_candidate conversion step.
   This role was never handled by any downstream pipeline, causing blocks to
   be downgraded to unknown_structural.

Validated on AH6Q7DLC (worst case, 30 blocks): 29/30 now correctly table_html,
1 remaining is reference_item (bibliography table, different classification).
Full corpus data pending rebuild.

585 figure/table/role tests pass.
2026-07-03 01:46:46 +08:00
LLLin000
8dba963394 fix: complete reference detection fixes — 5 problem families 2026-07-03 01:46:27 +08:00
LLLin000
9aa228d60c feat: layout robustness layer — robust banding, role-aware gating, two-column continuation
Task 1: Add robust band estimator core module (ocr_banding.py)
  - LayoutBandEstimate, UsableContentDecision dataclasses
  - collect_layout_band_candidates, estimate_layout_bands
  - choose_runtime_bands, decide_usable_content

Task 2: Expose dry-run robust band diagnostics in normalize_document_structure
  - layout_band_estimate field on DocumentStructure
  - Serialized diagnostics with legacy vs robust comparison

Task 3: Enable robust runtime band selection
  - _order_tail_blocks uses robust estimate + choose_runtime_bands
  - Falls back to legacy-safe bands when robust holds

Task 4: Make layout band gating role-aware
  - decide_usable_content replaces _is_in_usable_content
  - Strong roles (reference_heading/item/body, backmatter_heading/boundary)
    bypass band gating entirely
  - backmatter_body gated at Phase 4b after heading ownership check

Task 5: Column-aware two-column continuation reference attachment
  - _block_column(), _should_attach_reference_item_to_ref_section()
  - rejected_ref_items list replaces body_pool fallback

Verification: 202/202 tests pass, 14/14 paper rebuilds OK,
95FDVE4W reference ordering confirmed
2026-07-02 19:15:44 +08:00
LLLin000
bd3f3b63e9 fix: sidecar figure caption preservation + rotated table caption matching
37LK5T97 had two bugs:

1. Figure 1 sidecar demotion (ocr_document.py):
   - caption in left column (246px wide), image in right column
   - _is_near_figure_media uses h_overlap which fails for adjacent columns
   - candidate_resolution demoted the caption as 'narrative prose'
   - Fix: add _is_sidecar_candidate check — if figure_caption_candidate
     has vertical overlap with a media_asset but no horizontal overlap
     (adjacent columns), skip the prose-based demotion.
   - New narrow helper, does not change _is_near_figure_media (avoids
     regression in vision_footnote routing)

2. Rotated table caption matching (ocr_scores.py):
   - Tables 1-3 had rotated captions (dir=[0,-1], vertical text)
   - score_table_match only checked x_overlap + asset_below_caption
   - Rotated captions are beside the table body, not above it
   - Fix: when caption has rotated text and x_overlap < 0.5, use
     adjacent-column gap check + y_overlap ratio instead
   - Non-rotated captions unaffected (elif only fires for rotated)

Result: 37LK5T97 Figure 1 matched (figure_001, asset=block_id=9).
All 6 tables (3 rotated + 1 continuation x2 + 1 normal) matched.
428 regression tests pass.
2026-07-02 14:42:59 +08:00
LLLin000
d41712d7bc fix: normalize rotated figure captions before normal matching
- preserve PyMuPDF dir/wmode in span_metadata and bump span backfill version
- promote rotated vision_footnote figure descriptions into normal legend prematch
- normalize rotated caption/asset regions before score_figure_match
- carry rotation_correction_deg into matched figures and cropped renders
- use LANCZOS resampling on rotated figure crops to avoid aliasing
- keep synthetic fallback as last resort, reusing the same rotation metadata
2026-07-02 02:28:56 +08:00
LLLin000
61cbf51999 fix: use raw_label=reference_content as primary ref candidate signal
89.6% of papers (623/695) have all refs correctly labeled as
'reference_content' by the OCR model. The pipeline was ignoring
this signal and relying solely on regex patterns + a fragile
text heuristic (\b(?:19|20)\d{2}\. + commas), causing false
positives when grant numbers like '2023.02051.BD' matched.

New architecture:
1. raw_label=reference_content → immediate True (99.3% of refs)
2. marker regex patterns → fallback
3. text heuristic → emergency fallback for ~0.7% edge cases
   (refs labeled as plain 'text' by OCR, e.g. parenthesized '(2)')
2026-06-29 13:53:01 +08:00
LLLin000
55927b49f3 fix: tighten year-period regex in _is_reference_item_candidate to prevent false ref match on acknowledgment text
4AG67PBH P21: acknowledgment text absorbed as reference_item because:
  'doctoral scholarships 2023.02051.BD' matched \b(?:19|20)\d{2}\.
Fixed to \b(?:19|20)\d{2}\.(?:\s|$) — year+period must be followed by whitespace or end of string.

Left column now correct: body (Acknowledgments, Conflict, Keywords, dates) in body_zone,
then [1]-[3] in reference_zone. Right column all 33 refs unaffected.
2026-06-29 12:39:03 +08:00
Research Assistant
bea122b36f fix: prevent frontmatter_side_zone on/near reference boundary page
Blocks on ref page and 1 page before no longer get frontmatter_side_zone.
Fixes 'Conflict of Interest' heading on P21 (4AG67PBH) being assigned to
frontmatter_side_zone via _is_explicit_frontmatter_support_furniture path
that bypassed _is_heading_like_block check.

Adds page gate: first_reference_page is not None and page >= first_reference_page - 1
2026-06-29 00:02:35 +08:00
Research Assistant
d05623913b test: fix 25 stale test fixtures across 10 issues (616 OCR tests pass)
Pipeline fixes:
  - non_body_insert page-1 guard threshold: page_width * 0.2 -> 0.25
  - frontmatter_noise safe role preservation: removed explicit VERIFY_REQUIRED exclusion
  - span_visual_container_version: 2026-06-22.1 -> 2026-06-26.6

Test expectation updates:
  - non_body_insert: figure_caption test -> ponytail behavior
  - caffard abstract: accept Methods in main_ids as structured abstract subheading
  - legend_like role override: body_zone -> display_zone
  - backmatter heading render: **bold** -> ## heading
  - state machine: accept done_degraded as terminal status
  - body_zone anchor_family: relaxed assertion
  - truth surface docs: updated phrases for current phase
  - trace-vs-expectations: 10 assertions updated for post-P1 pipeline behavior
2026-06-28 23:19:47 +08:00
Research Assistant
71160ceca8 fix: frontmatter_main_zone held headings -> frontmatter_noise (no text matching)
Previous approach used hardcoded text patterns + position heuristics in
assign_block_role — fragile across journal layouts.

Current approach: after the structural gate holds a heading and the zone
is resolved, normalize any block that is:
  - in frontmatter_main_zone
  - role=unknown_structural (held by gate — no heading evidence)
  - seed_role in heading roles (section_heading, subsection_heading, etc.)
  - on page 1-2

...to frontmatter_noise. This uses only layout-derived signals (zone,
structural gate decision, seed_role), no text matching, no position/width
thresholds. Cannot accidentally eat legitimate headings because only
blocks the structural gate explicitly rejected are affected.
2026-06-28 22:17:14 +08:00
Research Assistant
68e0699b64 fix: three frontmatter resolution improvements from Gate 5 audit
1. _looks_like_initial_lastname_byline: require at least one lowercase letter.
   Prevents journal taglines like 'A RESEARCH VISION' from matching the
   initial-lastname pattern (e.g. 'J. Smith'). Fixes 24YKLTHQ block 7.

2. _is_first_page_body_start: metadata headings no longer trigger body_start.
   Page 1 section_heading for 'INFORMACIÓN DEL ARTÍCULO' (Article Information)
   is frontmatter metadata, not body content. Only known body-heading terms
   (introduction, methods, results, discussion, conclusion) trigger body_start.

3. build_document_abstract_span backward scan: exclude non-English keyword
   headings ('palabras clave', 'mots clés', 'schlagwörter') from the abstract
   body candidate set. Prevents Spanish keywords block from being collected
   as abstract body.

These changes fix 24YKLTHQ: block 17 (abstract) now correctly assigned
abstract_body instead of body_paragraph, block 7 no longer mislabeled as
authors, and page 1 frontmatter zone covers 5 more blocks.
2026-06-28 22:01:50 +08:00
Research Assistant
3b46419313 fix: strip pre-ref block IDs from tail_nonref_hold_zone in ref-partition path
When ref_partition_active=True, pre-ref blocks (pages before the reference zone)
must not inherit the stale tail_nonref_hold_zone from infer_zones(). The ref
partition correctly separates pre_ref/reference/post_ref, but _apply_zone_labels
re-applied the original region_bus zones, putting pre-ref body content back into
the tail zone.

Fix: before _apply_zone_labels, remove pre_ref block IDs (bare + _zone_block_key
format) from region_bus.tail_nonref_hold_zone's block_ids and composite_block_ids.
_apply_content_zone_fallback then correctly assigns body_zone to these blocks.

Fixes 4KCHGV2Z page 7 (20 blocks ALL previously in tail_nonref_hold_zone →
now correctly in body_zone/display_zone), and any similar case where pre-ref
body pages are adjacent to the reference zone.
2026-06-28 21:44:00 +08:00
Research Assistant
c15227076a backmatter: remove pre-ref disclosure normalization (pre-ref = body flow)
Confirmed with user: pre-ref disclosure headings (CRediT, Ethics) must stay
as subsection_heading in body flow. Removed _normalize_pre_ref_disclosure_runs
and related constants/functions. Updated tests (20 -> 16 cases).

5 audit papers verified:
  25K5KZAQ: body_end=11 ref_start=10 (was None)
  YGH7VEX6: body_end=8 ref_start=8
  NC66N4Q3: body_end=56 ref_start=56 (was None)
  9TW98JH8: body_end=5 ref_start=5
  XD2BPCMG: body_end=26 ref_start=21

318/318 tests pass.
2026-06-28 01:28:53 +08:00
Research Assistant
1a9ad1d147 P1: backmatter boundary redesign — ref-anchored partition replaces second _reconcile_tail_spread
reference_zone is the only hard boundary. Pre-ref disclosure headings (CRediT,
Ethics, Declaration) are normalized as local same-column runs — they never set
global boundaries. Four contracts enforced (Contract A-D in spec).

- 13 new helpers in ocr_document.py: _has_verified_reference_zone, _partition_by_reference_zone,
  _classify_same_page_block, _normalize_reference_roles_from_partition,
  _normalize_pre_ref_disclosure_runs, _build_tail_boundary_from_ref_partition, etc.
- infer_zones() stores effective_end_page for trimmed reference extent
- normalize_document_structure(): ref-partitioned path replaces second _reconcile_tail_spread;
  _promote_tail_body_candidates/_assign_tail_spread_ownership skipped when active
- 20 tests in test_backmatter_boundary.py
- 322/322 tests pass
2026-06-28 01:13:54 +08:00
Research Assistant
b1fdbc30eb feat: P0 fixes (ref sort, caption insert) + P1 figure containment render hygiene
P0:
- Fix 2: _ref_number_sort_key handles [N] bracket format (ocr_render.py:481)
- Fix 4: remove figure_caption from _INSERT_CANDIDATE_ROLES (ocr_document.py:3846)
- Fix 5: filter demoted body_paragraph from figure legends (ocr_figures.py:2950)

P1:
- figure containment render-hygiene pass after table inventory writeback
- 6 helpers: _cluster_bboxes_by_proximity, _is_contained,
  _highly_overlaps_any_matched_region, _figure_region_bbox,
  _matched_asset_keys, tag_figure_contained_text
- Wire call site in postprocess_ocr_result() (ocr.py:1891)

Infra:
- Sync workspace fulltext after rebuild (asset_index.py)
- Fix UnboundLocalError on index-refresh (sync_service.py)
- Add missing frontmatter fields to field registry (field_registry.yaml)
- 283 + 19 = 302 tests passing
2026-06-27 22:56:22 +08:00
Research Assistant
d018a68fd6 fix: harden early OCR frontmatter-side zone inference 2026-06-25 15:57:53 +08:00
Research Assistant
5bbe40d08b fix: reject weak isolated layout clusters 2026-06-22 22:15:12 +08:00
Research Assistant
6522dc9aef docs: update PROJECT-MANAGEMENT with 2HEUD5P9 rebuild findings 2026-06-22 02:50:37 +08:00
Research Assistant
da2e018b19 feat: figure merge clustering spec + fixes for caption dedup and structural gate
- Add global-distance-clustering figure merge design spec
- Fix ocr_roles.py: body_zone body_paragraph no longer promoted to
  figure_caption_candidate (prevents body mentions from entering legend pipeline)
- Fix ocr_document.py: preserve subsection_heading, reference_item,
  table_caption from structural-gate demotion
- Update PROJECT-MANAGEMENT.md with edge-case analysis
- Add ocr-vision-audit-master.md for CODE vs VISION separation
- Update SKILL.md dispatch rules with trust boundaries
2026-06-21 21:32:30 +08:00
Research Assistant
4d5180ee61 fix: restore post-boundary body text markers, add curly quote publisher note 2026-06-19 14:57:10 +08:00
Research Assistant
bcde1b0861 perf: simplify pre-ref backmatter to body classification, fix 3 residual bugs 2026-06-19 01:31:45 +08:00
Research Assistant
ae66732a64 refactor: move OCR ordering authority upstream with reference boundary enforcement 2026-06-18 22:19:14 +08:00
Research Assistant
c717084465 fix: conservatively tighten OCR tail and post-reference cleanup 2026-06-18 02:01:19 +08:00
Research Assistant
7a038d717d fix: reduce false OCR tail and backmatter conversions 2026-06-17 23:51:29 +08:00
Research Assistant
c737dae5f5 fix: split same-page OCR boundaries by reference heading position 2026-06-17 23:49:21 +08:00
Research Assistant
af11f6d985 fix: preserve panel labels and figure_inner_text in post-reference backmatter zone 2026-06-17 22:36:51 +08:00
Research Assistant
06f3c3e948 feat: add strong formal legends to accepted caption block IDs 2026-06-17 22:36:02 +08:00
Research Assistant
e8afb97401 feat: make caption candidate downgrade selective not global 2026-06-17 19:05:57 +08:00
Research Assistant
7fde6236a2 fix: skip backmatter section grouping on pages without ref heading
On two-column tail pages with reference items but no explicit reference
heading (e.g. K7R8PEKW page 16), forced backmatter section grouping
overrides the natural column-sorted reading order.

- _reorder_tail_run: add skip_section_grouping param. When set, emit
  blocks in column-sorted order with reference items grouped at end,
  bypassing the backmatter/body section grouping logic entirely.
- _order_tail_blocks: detect pages with ref items + no ref heading and
  pass skip_section_grouping=True.
- _normalize_backmatter_roles_after_boundary: remove forced conversion
  of body_paragraph to backmatter_body inside backmatter region (body
  paragraphs attach naturally via _find_owning_heading in Phase 1).
2026-06-17 01:31:38 +08:00
Research Assistant
8636a2cf89 fix: restore K7 abstract + Introduction paragraph 2026-06-16 21:33:03 +08:00
Research Assistant
644309473b fix: review findings — page1_candidates variable bug, page2 doc_title gate, same-page table pre-ref emission
- ocr_document.py: page1_candidates filter now uses b.get('block_id')
- ocr_structural_gate.py: page 2 doc_title requires source anchor to accept
- ocr_render.py: emit matched figures/tables before ## References to fix
  same-page table-after-refs ordering (A8E7SRVS Table 10)
2026-06-14 23:17:54 +08:00
Research Assistant
80b91f73fd fix(ocr): merge Box N / Key insights content boxes into structured_insert callout
Add _merge_box_content() post-processing to normalize_document_structure:
detects structured_insert blocks whose text starts with Box N or Key
insights, then consumes following body-like blocks as box content.
Consumed blocks get role=noise + render_default=False to suppress
double-rendering. Column-crossing boxes (common in multi-column
layouts) are handled by removing the x-overlap heuristic.
2026-06-14 22:16:57 +08:00
Research Assistant
f1cab1034e fix(ocr): prevent pre-proof watermark noise from leaking into backmatter rendering
In _normalize_backmatter_roles_after_boundary, respect upstream
seed_role classification for frontmatter_noise blocks. Only convert
frontmatter_noise to backmatter_body when seed_role differs (indicating
misclassification), not when it matches (indicating intentional
watermark/noise suppression).

Also remove internal reader_status labels (EXACT_MATCH, LEGEND_ONLY,
ASSET_GROUP_ONLY) from rendered fulltext output.
2026-06-14 21:02:33 +08:00
Research Assistant
5fc9554c66 fix(ocr): protect figure/image blocks from backmatter normalization, fix duplicate title, and add unified rebuild entry point
- ocr_render.py: extend consumed frontmatter roles skip from page 1 to pages <= 2,
  preventing paper_title appearing twice on pre-proof papers
- ocr_document.py: add image/media block promotion in backmatter zone normalization
  so raw image blocks become media_asset instead of backmatter_body; this fixes
  build_figure_inventory producing 0 matched_figures. Also check seed_role in
  _sanitize_reference_zone_boundary since it runs before role resolution
- scripts/dev/ocr_rebuild_paper.py: single-paper rebuild + block_trace regeneration
  entry point (will be wired into CLI later)
- tests: boundary protection and completeness check tests
- DW trace regenerated with correct role distribution
2026-06-14 18:47:50 +08:00
Research Assistant
fd73fd08ad fix(ocr): structural role and zone improvements
- CAQ: Move correspondence detection before zone-based furniture check
  (ocr_roles.py). Correspondence footnote now correctly gets
  frontmatter_support instead of frontmatter_noise.

- DW: Accept raw_label=doc_title on page 2 as paper_title repeat
  (ocr_structural_gate.py). Fixes title re-verification that was
  held to unknown_structural.

- DW: Tag keywords block as structured_insert when abstract span
  stop_reason=keywords (ocr_document.py). Uses abstract span boundary
  signal instead of text matching.

- Zone fallback: Fix unassigned role resolution in ref_heading_pages
  and last_body_heading_top pre-scans (ocr_document.py). Blocks with
  role=unassigned now fall through to seed_role. Also populate
  ref_heading_pages from first reference_item on pages without a
  reference_heading.

- Updated expectations for DW (7 FAIL, down from 9) and CAQ (0 FAIL,
  down from 3). All remaining DW FAILs are user-decided skips.
2026-06-14 15:16:06 +08:00
Research Assistant
53c8e7ea1b fix(ocr): post-ref backmatter zone normalizes all non-heading blocks to backmatter_body
In post_reference_backmatter_zone, blocks with role=reference_item are
outside the verified reference range. The structural gate cannot verify
them and holds them to unknown_structural. Fix: the zone normalization
now converts ALL non-heading blocks (including reference_item) to
backmatter_body, which the gate accepts via _SAFE_PRESERVED_ROLES.

DW expectations: 18 FAIL -> 9 FAIL (fixed 8 biography blocks + 1 equal
contribution note). Remaining 9 are pre-existing known bugs.
2026-06-14 13:50:06 +08:00
Research Assistant
24a00d5e6f fix: normalize biography tail blocks as backmatter 2026-06-14 12:28:51 +08:00
Research Assistant
f5ea3fe3de fix: promote confirmed backmatter heading candidates
Adds document-level promotion rules in normalize_document_structure() so
that backmatter_heading_candidate blocks get promoted to backmatter_heading
when followed by tail-like body text. Uses text evidence (conflict-of-interest
phrases, backmatter vocabulary) + local follower evidence (short body
paragraphs) — no paper-specific checks or literal page-number logic.

Closes task 6 of the OCR-v2 pipeline.
2026-06-14 12:21:45 +08:00
Research Assistant
0af59ae10d fix: split same-page body and reference tail zones 2026-06-14 12:09:46 +08:00
Research Assistant
3ae3ab62b5 fix: source anchor bridge, author matching overhaul, and gate overrides (Phase 11)
- Pass source_frontmatter_anchors to normalize_document_structure so gate
  has source-backed IDs before running (fixes paper_title/authors always HELD)
- Add table_caption CANDIDATE handler (was falling through to unknown_structural)
- Author matching: handle & splitting, strip trailing labels, initial-based
  matching for abbreviated names, subset match for truncated author lists
- Override frontmatter_noise preservation when seed_role is VERIFY_REQUIRED
- Accept authors role from source anchor regardless of seed_role
- Enrich source_metadata authors from formal-library.json fallback
- Heading merge: use vertical_gap <= 30px instead of sentence-end heuristic

All 280 tests pass. TSCKAVIS/DWQQK2YB/CAQNW9Q2 rebuilt and verified.
2026-06-14 02:37:48 +08:00
Research Assistant
465a89c4d5 fix: use max(backward_start, references_start) for spread_end 2026-06-13 21:41:21 +08:00
Research Assistant
56c549fec1 fix: remove blockquote prefix from abstract, fix backmatter_start detection 2026-06-13 21:40:03 +08:00
Research Assistant
050f9f32ee fix: suppress reader figure cards when embed covers them, add tail veto in infer_zones 2026-06-13 21:19:49 +08:00
Research Assistant
ce444c4845 fix: handle unassigned role in zone infer_zones and frontmatter boundary detection 2026-06-13 19:43:35 +08:00
Research Assistant
a7417b986e fix: use seed_role in zone fallback when role is unassigned 2026-06-13 19:34:25 +08:00
Research Assistant
b1c0764fe0 fix: bridge frontmatter anchors, add zone fallback, page-1 boundary, tail veto, and post-reference backmatter zone 2026-06-13 19:27:55 +08:00