Download scripts/qa_ingest_report.py from CamiloS1111/ragsolver: direct link, hf CLI and curl.
- Browser
- Download file 6.62 kB
-
https://huggingface.co/CamiloS1111/ragsolver/resolve/main/scripts/qa_ingest_report.py
- Command line
-
hf download hf://CamiloS1111/ragsolver/scripts/qa_ingest_report.py
-
curl -L -o qa_ingest_report.py https://huggingface.co/CamiloS1111/ragsolver/resolve/main/scripts/qa_ingest_report.py
6.62 kB
| """Genera docs/qa_ingest_<doc_id>.md a partir de ingest_report.json, figures.jsonl e | |
| informal.jsonl (docs/PLAN_RAG.md Fase 2, tarea 10). La rúbrica manual de las 15 | |
| descripciones de figura se completa a mano en el .md generado. | |
| SEC-2 (docs/AUDIT_2026-09-23.md, docs/PLAN_CORRECCIONES.md Fase 2): este reporte | |
| se publica (git, HF). Si el documento tiene `redistributable=false`, se redacta | |
| todo contenido literal derivado de su texto — fórmulas, transcripciones de | |
| huecos y descripciones de figura — y sólo quedan conteos y metadatos | |
| estructurales; mismo criterio que ya aplica `scripts/publish_hf.py` a las | |
| tablas `informal`/`figures` (no las sube si el documento no es redistribuible). | |
| Uso: python scripts/qa_ingest_report.py --doc-id edd3436ca148 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| from collections import Counter | |
| from pathlib import Path | |
| QA_GATE = (8, 16, 17, 22, 24, 37, 99) | |
| _REDACTED_NOTE = "Contenido derivado del documento redactado (`redistributable=false`); disponible localmente en `data/parsed/` (gitignored), nunca en git/HF." | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--doc-id", default="edd3436ca148") | |
| args = ap.parse_args() | |
| parsed = Path("data/parsed") / args.doc_id | |
| report = json.loads((parsed / "ingest_report.json").read_text(encoding="utf-8")) | |
| chunks = [json.loads(l) for l in open("data/corpus/informal.jsonl", encoding="utf-8") if l.strip()] | |
| figures = [json.loads(l) for l in open("data/corpus/figures.jsonl", encoding="utf-8") if l.strip()] | |
| cards = [c for c in chunks if c["unit_type"] == "problem_card"] | |
| by_n = {c["item_number"]: c for c in cards} | |
| doc_path = parsed / "document.json" | |
| redistributable = json.loads(doc_path.read_text(encoding="utf-8")).get("redistributable", False) if doc_path.exists() else False | |
| diffs = report.get("formula_diffs", []) | |
| accepted = [d for d in diffs if "after" in d] | |
| rejected = [d for d in diffs if "rejected" in d] | |
| areas = Counter(a for c in cards for a in c["math_area"]) | |
| fig_by_id = {f["figure_id"]: f for f in figures} | |
| lines = [ | |
| f"# QA de ingesta — `{args.doc_id}` (problemas resueltos olimpiadas.pdf)", | |
| "", | |
| "Generado por `scripts/qa_ingest_report.py`; criterios en `docs/PLAN_RAG.md` Fase 2, tarea 10.", | |
| "", | |
| "## Resumen", | |
| "", | |
| f"- Páginas: {report['n_pages']} · cards: {report['n_cards']} · **emparejadas problema↔solución: {report['n_paired']}/100** · chunks: {report['n_chunks']} (cards + figuras)", | |
| f"- Figuras recortadas: {report['n_figures_detected']} · con descripción VLM: {sum(1 for f in figures if f['description'])} · avisos red de seguridad vectorial: {len(report['figure_warnings'])}", | |
| f"- Huecos del layout transcritos por VLM: {len(report.get('gaps', []))} regiones", | |
| f"- QA de fórmulas: {len(accepted)} correcciones aplicadas, {len(rejected)} propuestas rechazadas por las guardas", | |
| f"- Llamadas al VLM/LLM en esta corrida (sin caché): {report['llm_calls']}", | |
| f"- Áreas (cards): {dict(areas.most_common())}", | |
| "", | |
| "## Gate 1 — emparejamiento problema↔solución (objetivo 100/100)", | |
| "", | |
| f"Resultado: **{report['n_paired']}/100**. Avisos: {report['warnings'] or 'ninguno'}.", | |
| "", | |
| "Notas del split por LLM:" if redistributable else f"Notas del split por LLM: {len(report.get('split_notes', []))}. {_REDACTED_NOTE}", | |
| *([f"- {n}" for n in report.get("split_notes", [])] if redistributable else []), | |
| "", | |
| "## Gate 2 — figuras de los problemas 8, 16, 17, 22, 24, 37, 99", | |
| "", | |
| ] | |
| if redistributable: | |
| lines += ["| Problema | Figuras | detected_by | Descripción (inicio) |", "|---|---|---|---|"] | |
| for n in QA_GATE: | |
| for fid in by_n[n]["figure_ids"]: | |
| f = fig_by_id.get(fid) | |
| if f: | |
| lines.append(f"| {n} | `{fid}` | {f['detected_by']} | {f['description'][:110]}… |") | |
| else: | |
| lines += [f"_{_REDACTED_NOTE} (las descripciones se generan a partir de las figuras del documento)._", "", "| Problema | Figuras | detected_by |", "|---|---|---|"] | |
| for n in QA_GATE: | |
| for fid in by_n[n]["figure_ids"]: | |
| f = fig_by_id.get(fid) | |
| if f: | |
| lines.append(f"| {n} | `{fid}` | {f['detected_by']} |") | |
| lines += [ | |
| "", | |
| "## Rúbrica manual — 15 descripciones (sí/no: ¿nombra todos los puntos?, ¿relaciones correctas?, ¿valores dados?)", | |
| "", | |
| "| # | Figura | Puntos | Relaciones | Valores | Observación |", | |
| "|---|---|---|---|---|---|", | |
| *[f"| {i+1} | `{f['figure_id']}` | | | | |" for i, f in enumerate(figures[:15])], | |
| "", | |
| "## Huecos del layout de MinerU recuperados por VLM", | |
| "", | |
| ] | |
| if redistributable: | |
| lines += [ | |
| "| Página | Capa de texto (evidencia) | Transcripción (inicio) |", | |
| "|---|---|---|", | |
| *[f"| {g['page']} | {g['text_layer'][:70]} | {g['transcription'][:90].replace(chr(10), ' ')} |" for g in report.get("gaps", [])], | |
| ] | |
| else: | |
| gap_pages = ", ".join(str(g["page"]) for g in report.get("gaps", [])) | |
| lines.append(f"{len(report.get('gaps', []))} regiones recuperadas (páginas: {gap_pages or '—'}). {_REDACTED_NOTE}") | |
| lines += [ | |
| "", | |
| "## QA de fórmulas (6b) — correcciones aplicadas", | |
| "", | |
| ] | |
| if redistributable: | |
| lines += ["| Página | Antes | Después |", "|---|---|---|", *[f"| {d['page']} | `{d['before'][:60]}` | `{d['after'][:60]}` |" for d in accepted]] | |
| else: | |
| pages = Counter(d["page"] for d in accepted) | |
| lines.append(f"{len(accepted)} correcciones en {len(pages)} páginas (páginas: {', '.join(str(p) for p in sorted(pages))}). {_REDACTED_NOTE}") | |
| lines += [ | |
| "", | |
| f"Rechazadas por las guardas (cambio sólo cosmético o absorción de texto vecino): {len(rejected)} — detalle en `data/parsed/{args.doc_id}/ingest_report.json` (local, gitignored).", | |
| "", | |
| "## Gold de OCR (10 páginas transcritas a mano)", | |
| "", | |
| "Pendiente: requiere transcripción manual humana (no se fabrica). Sustituto disponible hoy: las 5 correcciones reales detectadas por el VLM en 2 páginas de muestra (docs/DECISIONS.md #19).", | |
| "", | |
| ] | |
| out = Path("docs") / f"qa_ingest_{args.doc_id}.md" | |
| out.write_text("\n".join(lines), encoding="utf-8") | |
| print(out) | |
| if __name__ == "__main__": | |
| main() | |