#!/usr/bin/env python3 """Build corpus_index.json + eval.json for the MuDABench viewer. Reads the two source QA files (simple.json = concise final answers, complex.json = longer analytical final answers) and derives: - eval.json one entry per question (dataset-tagged); question, gold final answer, supporting facts (source_answer) and the supporting-document list (each with the value_* fields that question used + their schema descriptions). - corpus_index.json one entry per unique document (589 == the PDF corpus). Title = "symbol · year · doctype"; value_* fields are the union observed across every question that cites the doc; `pdf` points at the HF dataset CDN (streamed, not bundled). Run from the viewer repo root: python scripts/build_data.py Reads: simple.json, complex.json Writes: eval.json, corpus_index.json """ import json import os from collections import OrderedDict ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) SOURCES = [("simple", os.path.join(ROOT, "simple.json")), ("complex", os.path.join(ROOT, "complex.json"))] EVAL_OUT = os.path.join(ROOT, "eval.json") CORPUS_OUT = os.path.join(ROOT, "corpus_index.json") # PDFs live on the Hugging Face dataset; the viewer streams them from the CDN # so the Space never has to bundle the ~4 GB corpus. PDF_URL = "https://huggingface.co/datasets/Zhanli-Li/MuDABench/resolve/main/data/pdf/{id}.pdf" def value_fields(meta): """Return [(field_name, value), ...] for the value_* keys in a metadata row.""" out = [] for k, v in meta.items(): if k.startswith("value_"): out.append((k, v)) return out def doc_title(symbol, year, doctype): parts = [str(p) for p in (symbol, year, doctype) if p not in (None, "")] return " · ".join(parts) if parts else "(untitled)" def main(): eval_rows = [] # corpus[id] accumulates the merged view of a document across all questions. corpus = OrderedDict() for dataset, path in SOURCES: with open(path, encoding="utf-8") as f: data = json.load(f) for q in data: qid = q.get("question_id") src = q.get("source_answer") if isinstance(src, str): src = [src] if src.strip() else [] elif not isinstance(src, list): src = [] docs = [] for meta in q.get("metadata", []): did = meta.get("id") symbol = meta.get("symbol") year = meta.get("year") doctype = meta.get("doctype") schema = meta.get("schema", {}) or {} vfields = value_fields(meta) docs.append({ "id": did, "title": doc_title(symbol, year, doctype), "symbol": symbol, "year": year, "doctype": doctype, "fields": [ {"name": n, "value": v, "desc": schema.get(n, "")} for n, v in vfields ], }) if did is None: continue c = corpus.get(did) if c is None: c = corpus[did] = { "id": did, "symbol": symbol, "year": year, "doctype": doctype, "title": doc_title(symbol, year, doctype), "_fields": OrderedDict(), # name -> {"desc", "values"[]} "referenced_by": [], } for n, v in vfields: slot = c["_fields"].get(n) if slot is None: slot = c["_fields"][n] = {"desc": schema.get(n, ""), "values": []} if not slot["desc"] and schema.get(n): slot["desc"] = schema[n] if v not in slot["values"]: slot["values"].append(v) c["referenced_by"].append({"qid": qid, "dataset": dataset}) eval_rows.append({ "qid": qid, "dataset": dataset, "question": q.get("question", ""), "final_answer": q.get("final_answer", ""), "source_answer": src, "docs": docs, }) # finalize corpus entries corpus_rows = [] for c in corpus.values(): fields = [ {"name": n, "desc": slot["desc"], "values": slot["values"]} for n, slot in c["_fields"].items() ] corpus_rows.append({ "id": c["id"], "title": c["title"], "symbol": c["symbol"], "year": c["year"], "doctype": c["doctype"], "fields": fields, "n_refs": len(c["referenced_by"]), "referenced_by": c["referenced_by"], "pdf": PDF_URL.format(id=c["id"]), }) corpus_rows.sort(key=lambda d: ( str(d["doctype"] or ""), str(d["symbol"] or ""), str(d["year"] or "") )) # collision report (docs sharing an identical title) seen = {} collisions = 0 for d in corpus_rows: seen.setdefault(d["title"], []).append(d["id"]) for title, ids in seen.items(): if len(ids) > 1: collisions += 1 with open(EVAL_OUT, "w", encoding="utf-8") as f: json.dump(eval_rows, f, ensure_ascii=False, indent=0) with open(CORPUS_OUT, "w", encoding="utf-8") as f: json.dump(corpus_rows, f, ensure_ascii=False, indent=0) print(f"wrote {EVAL_OUT}: {len(eval_rows)} questions") print(f"wrote {CORPUS_OUT}: {len(corpus_rows)} documents " f"({collisions} title-collision groups)") from collections import Counter dt = Counter(d["doctype"] for d in corpus_rows) print("doctypes:", dict(dt)) if __name__ == "__main__": main()