"""Load extraction-stage table JSON using existing WaterScope media resolver. Not a validated cell grid when structure_extracted=False. Preserve PDF word coordinates, original strings and extraction quality flags; never silently interpret position-only data as exact, verified spreadsheet cells. """ from __future__ import annotations import json from pathlib import Path from typing import Callable, Any def load_table_evidence(folder: str, label: str, resolver: Callable[[str,str], Any]) -> dict | None: manifest_path = resolver(folder, "manifest.json") if manifest_path is None: return None manifest = json.loads(Path(manifest_path).read_text(encoding="utf-8-sig")) matches = [x for x in manifest.get("items", []) if x.get("label") == label and x.get("kind") == "table"] if len(matches) != 1: return None filename = matches[0].get("data_file") if not filename or Path(filename).name != filename or not filename.lower().endswith(".json"): return None path = resolver(folder, filename) if path is None: return None raw = Path(path).read_bytes() if len(raw) > 3_000_000: raise ValueError("Table JSON exceeds 3MB limit") table = json.loads(raw.decode("utf-8-sig")) if table.get("kind") != "table" or table.get("label") != label: raise ValueError("Table JSON does not match manifest label") rows = table.get("text_rows") or [] cells = table.get("cells") if not isinstance(rows, list) or len(rows) > 500: raise ValueError("Invalid/oversized table text rows") # Preserve every source row, but replace redundant per-word bounding boxes # with a compact word@horizontal-position representation. A text row is NOT # a verified table cell row, especially for merged/multi-line groups. compact_rows = [] for row in rows: words = [] for word in (row.get("words") or [])[:100]: bbox = word.get("bbox_points") or [] token = str(word.get("text", ""))[:120] if len(bbox) == 4: words.append([token, round(float(bbox[0]), 1)]) else: words.append([token, None]) compact_rows.append({"text": str(row.get("text", ""))[:1600], "positioned_words": words}) # Include all nonempty rows, including continuation rows and classifications. # The original JSON remains in the dataset; this is only an inference view. return { "label": label, "data_file": filename, "structure_extracted": bool(table.get("structure_extracted")), "row_count": table.get("row_count"), "column_count": table.get("column_count"), "cells": cells if table.get("structure_extracted") and isinstance(cells, list) else None, "text_rows": compact_rows, "source_quality": "structured_cells" if table.get("structure_extracted") and isinstance(cells,list) else "positioned_text_unverified", }