timchen0618's picture
Load corrected PhantomWiki schema-v3 structures
a5f362d verified
Raw
History Blame Contribute Delete
13.4 kB
#!/usr/bin/env python3
"""Build a static cross-dataset source-document and scaffold viewer."""
from __future__ import annotations
import argparse
import csv
import hashlib
import json
import shutil
from pathlib import Path
from typing import Any, Iterable
ROOT = Path(__file__).resolve().parents[1]
INFO = Path("/home/azureuser/projects/information-scaffolds")
SESSION_INPUTS = Path(
"/home/azureuser/.copilot/session-state/6a2da107-0228-4316-ad25-f248cf796604/"
"files/structure-generation-viewer-inputs"
)
DATA_ROOT = Path("/mnt/ramdisk/blobstore/timchen0618/data")
SHAPE_ALIASES = {
"tabular_records": "tables",
"tables": "tables",
"relation_graphs_and_mappings": "knowledge_graphs",
"knowledge_graphs": "knowledge_graphs",
"claim_and_theme_summaries": "claims",
"claims": "claims",
"chronology_and_timeline_indexes": "timelines",
"timelines": "timelines",
"qa_shortcuts_and_templates": "legacy_qa_shortcuts",
}
SHAPE_LABELS = {
"tables": "Tables",
"knowledge_graphs": "Knowledge graphs",
"claims": "Claims",
"timelines": "Timelines",
"legacy_qa_shortcuts": "Legacy QA shortcuts",
}
DATASETS = {
"monaco_dev": {
"label": "MoNaCo-dev",
"source": {
"kind": "raw_dir",
"path": INFO / "outputs/rawtext_corpus_monacodev/monaco_dev/scaffolds",
},
"runs": [
{
"id": "legacy_full",
"label": "Legacy full extraction - 50-doc subset",
"job": "sweet_screw_frnspf41x5",
"schema": "legacy five-shape",
"root": INFO
/ "outputs/e2e_dev_scaffolds/monaco_dev/named-outputs/scaffolds_dir",
},
{
"id": "current",
"label": "Current schema-v3 extraction",
"job": "quirky_cabbage_zdpp543s3z",
"schema": "3",
"root": SESSION_INPUTS / "monaco_dev/current/scaffolds_dir",
"quality": SESSION_INPUTS / "monaco_dev/current/schema_quality_report/report",
},
],
},
"wiki_opentable_dev": {
"label": "Open-WikiTable-dev",
"source": {
"kind": "raw_dir",
"path": INFO
/ "outputs/rawtext_corpus_wikiotdev/wiki_opentable_dev/scaffolds",
},
"runs": [
{
"id": "legacy_full",
"label": "Legacy full extraction - 50-doc subset",
"job": "cyan_muscle_ylws4v86bz",
"schema": "legacy five-shape",
"root": INFO
/ "outputs/e2e_dev_scaffolds/wiki_opentable_dev/named-outputs/scaffolds_dir",
},
{
"id": "current",
"label": "Current schema-v3 extraction",
"job": "purple_eye_8f31ymzz71",
"schema": "3",
"root": SESSION_INPUTS / "wiki_opentable_dev/current/scaffolds_dir",
"quality": SESSION_INPUTS
/ "wiki_opentable_dev/current/schema_quality_report/report",
},
],
},
"phantom_wiki": {
"label": "PhantomWiki",
"source": {
"kind": "corpus",
"path": DATA_ROOT / "eval/phantom_wiki/unified/corpus.unified.jsonl",
},
"runs": [
{
"id": "legacy_full",
"label": "Legacy full extraction - 50-doc subset",
"job": "teal_jicama_qxvpyf3jsd",
"schema": "legacy five-shape",
"root": INFO
/ "outputs/e2e_runs/new-datasets-full-20260711/phantom_wiki/"
"named-outputs/scaffolds_dir",
},
{
"id": "current",
"label": "Current schema-v3 extraction",
"job": "mango_avocado_7qnv0vlf46",
"schema": "3",
"root": SESSION_INPUTS / "phantom_wiki/current/scaffolds_dir",
"quality": SESSION_INPUTS
/ "phantom_wiki/current/schema_quality_report/report",
},
],
},
}
def read_json(path: Path) -> Any:
return json.loads(path.read_text(encoding="utf-8"))
def read_jsonl(path: Path) -> Iterable[dict[str, Any]]:
with path.open(encoding="utf-8") as handle:
for line_number, line in enumerate(handle, 1):
if not line.strip():
continue
value = json.loads(line)
if not isinstance(value, dict):
raise ValueError(f"{path}:{line_number}: expected an object")
yield value
def parse_doc_id(text: str, fallback: str) -> str:
for raw in text.splitlines():
line = raw.strip()
if not line:
continue
if line.startswith("#") and line.lstrip("#").strip().lower().startswith("id:"):
value = line.lstrip("#").strip().split(":", 1)[1].strip()
return value or fallback
return fallback
return fallback
def load_selected_sources(source: dict[str, Any]) -> list[dict[str, str]]:
path = Path(source["path"])
if source["kind"] == "raw_dir":
selected: list[Path] = []
for candidate in path.rglob("*.txt"):
if candidate.is_file():
selected.append(candidate)
if len(selected) == 50:
break
selected.sort()
rows = []
for candidate in selected:
text = candidate.read_text(encoding="utf-8")
rows.append(
{
"doc_id": parse_doc_id(text, candidate.stem),
"contents": text,
"source_path": str(candidate.relative_to(path)),
}
)
return rows
rows = []
for row in read_jsonl(path):
rows.append(
{
"doc_id": str(row["id"]),
"contents": str(row.get("contents", "")),
"source_path": str(row["id"]),
}
)
if len(rows) == 50:
break
return rows
def artifact_format(filename: str) -> str:
if filename.endswith(".edges.jsonl"):
return "jsonl"
if filename.endswith(".timeline.json"):
return "json"
suffix = Path(filename).suffix.lower().lstrip(".")
return suffix if suffix in {"csv", "json", "jsonl", "md"} else "text"
def load_quality(path: Path | None) -> dict[str, Any] | None:
if path is None or not path.exists():
return None
return read_json(path)
def load_scaffold_run(config: dict[str, Any]) -> dict[str, Any]:
if config.get("status") == "pending":
return {
"id": config["id"],
"label": config["label"],
"job": config["job"],
"schema": config["schema"],
"status": "pending",
"documents": {},
"quality": None,
}
root = Path(config["root"])
top = read_json(root / "_index.json")
documents: dict[str, list[dict[str, Any]]] = {}
shape_summaries = []
for shape in top.get("shapes", []):
raw_shape = str(shape["shape_id"])
canonical_shape = SHAPE_ALIASES.get(raw_shape, raw_shape)
folder = str(shape.get("folder") or raw_shape).rstrip("/")
shape_index = read_json(root / folder / "_index.json")
shape_summaries.append(
{
"id": canonical_shape,
"raw_id": raw_shape,
"label": SHAPE_LABELS.get(canonical_shape, shape.get("display_name", raw_shape)),
"description": shape_index.get("description", shape.get("definition", "")),
"n_files": len(shape_index.get("entries", [])),
}
)
for entry in shape_index.get("entries", []):
doc_id = str(entry["doc_id"])
filename = str(entry["file"])
artifact_path = root / folder / filename
documents.setdefault(doc_id, []).append(
{
"shape": canonical_shape,
"shape_label": SHAPE_LABELS.get(canonical_shape, canonical_shape),
"raw_shape": raw_shape,
"filename": filename,
"format": artifact_format(filename),
"unit_name": entry.get("unit_name"),
"unit_description": entry.get("unit_description"),
"content": artifact_path.read_text(encoding="utf-8"),
}
)
return {
"id": config["id"],
"label": config["label"],
"job": config["job"],
"schema": config["schema"],
"status": "ready",
"documents": documents,
"quality": load_quality(config.get("quality")),
"summary": {
"n_docs_seen": top.get("n_docs_seen"),
"n_files_written": top.get("n_files_written"),
"shapes_hash": top.get("shapes_hash"),
"shape_summaries": shape_summaries,
"validation_metrics": top.get("validation_metrics"),
},
}
def record_name(doc_id: str) -> str:
return hashlib.sha1(doc_id.encode()).hexdigest() + ".json"
def build_dataset(dataset_id: str, config: dict[str, Any], data_dir: Path) -> dict[str, Any]:
sources = load_selected_sources(config["source"])
if len(sources) != 50:
raise ValueError(f"{dataset_id}: expected 50 source documents, got {len(sources)}")
source_ids = [row["doc_id"] for row in sources]
if len(set(source_ids)) != 50:
raise ValueError(f"{dataset_id}: selected source IDs are not unique")
runs = [load_scaffold_run(run) for run in config["runs"]]
records_dir = data_dir / "records" / dataset_id
records_dir.mkdir(parents=True, exist_ok=True)
index_rows = []
for position, source in enumerate(sources, 1):
doc_id = source["doc_id"]
run_records = {}
for run in runs:
artifacts = run["documents"].get(doc_id, [])
run_records[run["id"]] = {
"status": run["status"],
"artifacts": artifacts,
"n_artifacts": len(artifacts),
}
digest = hashlib.sha256(source["contents"].encode()).hexdigest()
record = {
"dataset": dataset_id,
"dataset_label": config["label"],
"position": position,
"doc_id": doc_id,
"source": {
"contents": source["contents"],
"source_path": source["source_path"],
"sha256": digest,
},
"runs": run_records,
}
filename = record_name(doc_id)
(records_dir / filename).write_text(
json.dumps(record, ensure_ascii=False), encoding="utf-8"
)
index_rows.append(
{
"position": position,
"doc_id": doc_id,
"record": f"data/records/{dataset_id}/{filename}",
"source_preview": source["contents"].replace("\n", " ")[:180],
"source_sha256": digest,
"run_counts": {
run_id: value["n_artifacts"] for run_id, value in run_records.items()
},
}
)
return {
"id": dataset_id,
"label": config["label"],
"n_docs": len(sources),
"runs": [
{
key: run.get(key)
for key in ("id", "label", "job", "schema", "status", "quality", "summary")
}
for run in runs
],
"records": index_rows,
}
def validate_bundle(index: dict[str, Any], data_dir: Path) -> None:
if len(index["datasets"]) != 3:
raise ValueError("expected three datasets")
for dataset in index["datasets"]:
if dataset["n_docs"] != 50 or len(dataset["records"]) != 50:
raise ValueError(f"{dataset['id']}: expected 50 records")
if len({row["doc_id"] for row in dataset["records"]}) != 50:
raise ValueError(f"{dataset['id']}: duplicate document IDs")
for row in dataset["records"]:
path = ROOT / row["record"]
if not path.exists():
raise ValueError(f"missing record shard: {path}")
phantom = next(row for row in index["datasets"] if row["id"] == "phantom_wiki")
current = next(run for run in phantom["runs"] if run["id"] == "current")
if current["status"] != "ready":
raise ValueError("PhantomWiki current run must be ready")
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--out", type=Path, default=ROOT / "data")
args = parser.parse_args()
if args.out.exists():
shutil.rmtree(args.out)
args.out.mkdir(parents=True)
index = {
"title": "Structure Generation Prompt Viewer",
"datasets": [
build_dataset(dataset_id, config, args.out)
for dataset_id, config in DATASETS.items()
],
}
(args.out / "index.json").write_text(
json.dumps(index, ensure_ascii=False, indent=2) + "\n", encoding="utf-8"
)
validate_bundle(index, args.out)
print(
"Built 150 document records: "
+ ", ".join(f"{row['label']}={row['n_docs']}" for row in index["datasets"])
)
return 0
if __name__ == "__main__":
raise SystemExit(main())