Three issues fixed:
1. score_structured_insert: _in_visual_container weight 0.3->0.45
(evidence-driven admission is now reliable)
2. score_structured_insert: add sidebar_header_phrase check (+0.2)
for 'available with this article' etc.
3. _container_text set to block text (was True bool)
4. Normalize newlines in text before keyword matching
5. Safeguard renderer for non-string _container_text
6. Bump span_visual_container_version to force backfill re-run
Add _infer_missing_main_figure_numbers() that fills figure_number=None
for matched main-sequence figures when a single leading gap (Figure 1)
can be inferred with high confidence.
- _FRONTMATTER_VISUAL_VETO + _has_frontmatter_visual_veto()
- _FIGURE_MARKER_PATTERN regex + _extract_figure_marker()
- _coerce_int_figure_number(), _resolve_legend_bbox()
- _infer_missing_main_figure_numbers() wired into build_figure_inventory()
- 9 test cases covering acceptance, veto, isolation, skip reasons
- Fix golden vault test key drift (aliases, ocr_time, etc.)
- Add supersede note for spec's old integration wording
- Replace _FIGURE_NUMBER_PATTERN with new _FIGURE_MARKER_PATTERN regex (captures S prefix)
- Add _coerce_int_figure_number() helper (handles int/float/str from JSON)
- Add duplicate_known_main_numbers skip reason
- _FRONTMATTER_VISUAL_VETO: remove bare 'toc' substring risk
- Add _has_frontmatter_visual_veto() helper with word-boundary match
- Noise check: resolvable order key failure strategy
- Stage Boundary: include inventory[figure_number_inference] in mutation scope
- Use _format_figure_id() not literal string
- Add test_infer_duplicate_known_numbers_skips test case
- Frontmatter veto test: veto item must otherwise be eligible
Skip only thin-border unfilled rects with near-gray color and
low brightness — these are PyMuPDF get_drawings() false positives
from clipping paths. Keep colored thin borders (legitimate boxes)
and pure black thin borders (legitimate black frames).
Added 2 tests (colored border kept, black border kept).
Verified via 50-paper audit: all 14 dark-gray rects filtered,
all colored/black rects preserved.
backfill_span_metadata_from_pdf only SET _in_visual_container=True
on blocks overlapping a container. When containers are no longer
detected (filter improvement), leftover True persisted. Fix:
pop the key when containers list is empty for the page.
Also bump CURRENT_SPAN_VISUAL_CONTAINER_VERSION to force re-backfill.
N6XCZD25's body paragraphs on pages 3+ were misclassified as
structured_insert because a 0.5pt unfilled page-decoration border
rectangle (covering 80% of the page) was detected as a container.
Filter: skip bordered+unfilled rectangles with stroke_width <= 1pt
that cover >50% of page area — these are text-area frames, not
callout boxes.
When two figures share the same figure_id (e.g. supplementary
"Figure S.1" and main "Figure 1" both mapped to "figure_001"),
the second occurrence is renamed with an "s" prefix:
figure_001 -> figure_s001, figure_ss001, etc.
This avoids file overwrite in ocr_objects.py and ensures both
body and supplementary figures appear at their correct positions
in the rendered fulltext.
Changes:
- _resolve_figure_id_collisions() in ocr_figures.py
- figure_id passthrough in ocr_figure_reader.py (reader + normalize)
- _reader_figure_embed_target prefers explicit figure_id
- 11 unit tests covering single/triple/multi collisions, mixed buckets,
empty IDs, realistic body+supplementary layout
Moves the block_id fetch and consumed_table_block_keys check before
_SKIPPED_BODY_ROLES so table note removal is a contract-based decision
(ownership) not a role-based accident (footnote role).
Part of table ownership contract refactor.
- _strip_caption_number_prefix + _normalized_caption_body helpers
- When same figure number but different caption body (and not bundle-source),
retain both as distinct legends instead of deduping
- Fixes 2UIPV93M page 49 appendix figures silently removed by dedup
- Bundle-source dedup (DWQQK2YB pattern) unaffected
- same_number_distinct_legends audit surface added to inventory
- 217 tests passed, 0 failures
- Changed _reserve_cross_page_objects to return set[tuple[int, str]]
instead of set[str] so reused block_id across pages does not
overwrite reserved legends (VFS8CBW2 page 33 vs 38 both used block_id=1)
- Updated _settle_cross_page_reserved_objects to key legends_by_id
by (page, block_id)
- Fix: reserved legends no longer filtered by caption-band assist on
competing-caption pages, so deferred hypotheses still emit
- Fix: reservation test assertion updated for tuple format
- Fix: competing-caption veto test updated for band-scoped parent
acceptance