When figure_caption_candidate blocks lack explicit marker_type or caption_text in the strict inventory, _normalize_bucket now falls back to the structured block's text and infers figure_number/marker_type from the text content. This ensures candidate captions propagate through the reader-figure pipeline to the renderer and health summary.
_index_structured_blocks used block_id as dict key, but PaddleOCR assigns
the same block_id for blocks on different pages. When A8E7SRVS page 6
block_id=5 (Fig.5) overwrites page 5 block_id=5 (Fig.1), reader output
rendered Figure 5 with Fig.1 caption text.
Add _index_blocks_by_page_and_id using (page, block_id) tuples for
page-aware lookup in _normalize_bucket. Keep serialized block_index
clean (plain int|str keys) to preserve JSON compatibility.
Add regression test for cross-page block_id collision.
- _stable_reader_figure_id: deterministic ID generation for reader figures
(figure_number-based or visual_group fallback with page+asset)
- synthesize_reader_figures: shell that normalizes inventory, materializes
reader figures with separate reader_status (RESOLVED/GROUPED_APPROXIMATE/
DEFERRED/CAPTION_ONLY/ORPHAN_ASSETS) and strict_status (preserved from
source bucket)
- _materialize_reader_figure: maps normalized items to reader figure dicts
- ReaderCoverage dataclass extended with as_dict()
- Tests: status separation, stable ID generation, normalization round-trip