feat: persist OCR reader figure artifacts and health metrics

This commit is contained in:
Research Assistant 2026-06-10 20:43:54 +08:00
parent 7ec1f66da7
commit c86700b6f2
4 changed files with 65 additions and 0 deletions

View file

@ -1846,6 +1846,14 @@ def postprocess_ocr_result(vault: Path, key: str, all_results: list[dict]) -> tu
figure_inventory,
)
# --- Phase 2a: reader figure synthesis ---
from paperforge.worker.ocr_figure_reader import synthesize_reader_figures
reader_payload = synthesize_reader_figures(figure_inventory, structured_blocks=structured)
reader_figures_dir = ocr_root / "structure"
reader_figures_dir.mkdir(parents=True, exist_ok=True)
write_json(reader_figures_dir / "reader_figures.json", reader_payload)
# --- Phase 2: table inventory ---
table_inventory = build_table_inventory(structured)
write_table_inventory(
@ -1903,6 +1911,7 @@ def postprocess_ocr_result(vault: Path, key: str, all_results: list[dict]) -> tu
figure_inventory=figure_inventory,
table_inventory=table_inventory,
doc_structure=doc_structure,
reader_payload=reader_payload,
)
write_ocr_health(ocr_root / "health", health_report)
meta["ocr_health_overall"] = health_report["overall"]

View file

@ -57,6 +57,7 @@ def build_ocr_health(
figure_inventory: dict,
table_inventory: dict,
doc_structure: Any = None,
reader_payload: dict | None = None,
) -> dict[str, Any]:
section_heading_count = sum(1 for b in structured_blocks if b.get("role") == "section_heading")
abstract_found = any(
@ -224,6 +225,14 @@ def build_ocr_health(
else 1.0
),
}
if reader_payload is not None:
rc = reader_payload.get("reader_coverage", {})
report["figure_reader_coverage_total"] = rc.get("total", 0)
report["figure_reader_coverage_accounted"] = rc.get("accounted", 0)
report["figure_reader_coverage_gap_count"] = rc.get("gap_count", 0)
report["figure_reader_coverage_ratio"] = rc.get("ratio", 1.0)
report.update(decision_summary)
degraded_reasons = []
@ -278,6 +287,8 @@ def build_ocr_health(
degraded_reasons.append(
f"figure legend completeness gap ({formal_legend_gaps} numbered legends unaccounted for)"
)
if reader_payload is not None and reader_payload.get("reader_coverage", {}).get("gap_count", 0) > 0:
degraded_reasons.append("reader_figure_coverage_gap")
return report

View file

@ -130,6 +130,14 @@ def run_derived_rebuild_for_keys(vault: Path, keys: list[str]) -> dict:
figure_inventory = build_figure_inventory(structured)
write_figure_inventory(artifacts.blocks_structured.parent / "figure_inventory.json", figure_inventory)
# Rebuild reader figures
from paperforge.worker.ocr_figure_reader import synthesize_reader_figures
reader_payload = synthesize_reader_figures(figure_inventory, structured_blocks=structured)
reader_figures_dir = paper_root / "structure"
reader_figures_dir.mkdir(parents=True, exist_ok=True)
write_json(reader_figures_dir / "reader_figures.json", reader_payload)
# Rebuild table inventory
from paperforge.worker.ocr_tables import build_table_inventory, write_table_inventory
@ -189,6 +197,7 @@ def run_derived_rebuild_for_keys(vault: Path, keys: list[str]) -> dict:
figure_inventory=figure_inventory,
table_inventory=table_inventory,
doc_structure=doc_structure,
reader_payload=reader_payload,
)
write_ocr_health(paper_root / "health", health_report)

View file

@ -202,3 +202,39 @@ def test_legend_only_consumes_caption_when_rendered() -> None:
assert result["consumed_caption_block_ids"] == [21]
assert result["reader_figures"][0]["reader_status"] == "LEGEND_ONLY"
def test_reader_payload_coverage_accounted_matches_reader_figures() -> None:
from paperforge.worker.ocr_figure_reader import synthesize_reader_figures
strict_inventory = {
"matched_figures": [
{
"figure_number": 1,
"legend_block_id": 5,
"text": "Fig. 1 Overview of the system...",
"matched_assets": [{"block_id": 10, "bbox": [1, 2, 3, 4]}],
"match_score": 0.91,
"marker_type": "figure_number",
}
],
"held_figures": [],
"ambiguous_figures": [],
"unmatched_legends": [
{
"figure_number": 2,
"legend_block_id": 21,
"text": "FIGURE 2 | Treadmill exercise protocols...",
"marker_type": "figure_number",
}
],
"unresolved_clusters": [],
}
result = synthesize_reader_figures(strict_inventory, structured_blocks=[])
assert result["reader_coverage"]["total"] == 2
assert result["reader_coverage"]["accounted"] == 2
assert result["reader_coverage"]["gap_count"] == 0
assert result["reader_coverage"]["ratio"] == 1.0
assert set(result["consumed_caption_block_ids"]) == {5, 21}