PR-1 of the architecture review fixes.
Two entry-level filters before figure matching:
1. _is_table_owned_media(): excludes media_asset blocks with table
roles/raw_labels/hints from the figure asset pool. Previously these
entered as figure candidates and ended up as unmatched legends/orphans.
2. _looks_like_inline_figure_body_reference(): detects body text like
'Figure 11-10 shows...' (hyphenated ranges, list ranges) and flags
it as an inline reference, not a figure legend. Applied in both
ocr_figures.py and ocr_roles.py for consistent early rejection.
Verified on real vault:
SKXTCE6M: unmatched_legends 6→0, overall yellow→green
SRNJDAA2: unmatched_legends 9→0
Tests: 384 pass (+9 new), 0 fail
- Add _FIGURE_DESCRIPTION_OPENING_PATTERN for 'This figure...' / 'Figure N' etc.
- Insert rescue before generic footnote fallback
- Route to figure_caption (near media) or figure_caption_candidate (far)
- _TABLE_PREFIX_PATTERN in ocr_roles.py, ocr_signatures.py, ocr_tables.py
now matches Table I/II/III and Table S1/S2
- _extract_table_number and _extract_marker_signature parse Roman numbers
- figure_title generic fallback checks table prefix before figure caption
- ocr_families.py adds early table-prefix guard before reference family
Running headers at y<6% page height with pre-proof text are now
frontmatter_noise regardless of page number or raw_label, preventing
PaddleOCR mislabeling header as paragraph_title from inserting
page-furniture text into the body heading hierarchy
- _PREPROOF_MARKER suppression now guarded by page==1, y_top>8%, raw_label=paragraph_title, not_header, not_footer
- _is_preproof_marker() exported for health use
- rescue_roles_with_document_context: pre-proof frontmatter_noise blocks are exempt from body_paragraph rescue
- Page-1 title fallback: when pre-proof steals the title zone, next substantial text block becomes paper_title
- Tests: running header not suppressed, page-2 not suppressed, variants pass
- Frontiers figure title: _FRONTIERS_FIGURE_TITLE_PATTERN seeds FIGURE N | Title
as figure_caption even when raw_label=text (Frontiers/journals style)
- Panel label exclusion: _PANEL_LABEL_PATTERN sends A/B/C/(D)/E. to
figure_inner_text before they enter the caption/matching pipeline
- Page 1 heading override: _explicit_scholarly_heading_role() detects Roman
numeral (I. INTRODUCTION) and alpha (A. Materials) headings with y_top
guard to protect frontmatter titles
- Layout audit severity: _run_layout_audit() separates anomalies into
info/warning/error; full-width headings exempted; health only degraded
when error_count > 0
- first_author fallback: _enrich_meta_from_paper_note() falls back to
first_author when authors field is missing; _normalize_author_name()
strips {superscript} markers for fuzzy matching
- Add RegionPrepass data model for frontmatter/structured_insert/body classification
- Add PDF visual container detection (filled rects) for sidebar detection
- Add Box N and keyword-based sidebar anchor detection
- Add column-aware block expansion for mixed sidebar clusters
- Add _container_text from PDF text blocks for cross-column truncation
- Add degraded-mode heading level normalization with font size clustering
- Merge adjacent structured_insert blocks into single callout
- Fix metadata enrichment from Literature-hub note frontmatter for old papers
- Fix heading hierarchy using span font-size clusters when available
- Add real-paper regression harness (TSCKAVIS, CAQNW9Q2, A8E7SRVS, K7R8PEKW)
- Add body retention, heading, metadata, and callout acceptance tests
- 219 passed, 4 control guards passed
- build_structured_blocks now returns (rows, doc_structure) tuple
- ocr.py and ocr_rebuild.py pass document_structure to render_fulltext_markdown
- bare except Exception replaced with structured logging