spb/traite-evaluation-qc
Public
1#!/usr/bin/env python32"""Vérifications du manuscrit : stubs restants, figures manquantes, scripts des figures,3citations/références non définies (log), \\synth sans encadré, équations hors chapitre, CSV de vérification.4Usage : tools/check_manuscript.py [--log build/main.log] [--no-log] [--strict]"""5import re, sys, csv, pathlib, argparse6ROOT = pathlib.Path(__file__).resolve().parents[1]7ap = argparse.ArgumentParser(); ap.add_argument("--log"); ap.add_argument("--no-log", action="store_true"); ap.add_argument("--strict", action="store_true")8a = ap.parse_args()9problems, notes = [], []10chapters = sorted((ROOT/"manuscript/chapters").glob("ch*.tex"))11apps = sorted((ROOT/"manuscript/appendices").glob("*.tex"))12texts = {p: p.read_text(encoding="utf-8") for p in chapters + apps}13# 1. stubs14stubs = [p.name for p, t in texts.items() if "\\stubchapter" in t]15notes.append(f"{len(chapters)-sum(1 for s in stubs if s.startswith('ch'))}/{len(chapters)} chapitres rédigés ; stubs restants : {', '.join(stubs) or 'aucun'}")16if a.strict and stubs: problems.append(f"stubs restants : {stubs}")17# 2. figures incluses existent + inscrites dans verification/figures.csv18figreg = {}19fcsv = ROOT/"verification/figures.csv"20if fcsv.exists():21 for row in csv.DictReader(fcsv.open(encoding="utf-8")): figreg[row["fichier"]] = row22incl = re.compile(r"\\includegraphics(?:\[[^\]]*\])?\{([^}]+)\}")23for p, t in texts.items():24 for f in incl.findall(t):25 cands = [ROOT/"assets/figures"/f, ROOT/"assets/figures"/(f+".pdf"), ROOT/"assets/figures"/(f+".png")]26 if not any(c.exists() for c in cands): problems.append(f"{p.name}: figure absente {f}")27 key = f if f.endswith((".pdf",".png")) else f+".pdf"28 if key not in figreg and not f.startswith("logo"): problems.append(f"{p.name}: figure {f} non inscrite dans verification/figures.csv")29for key, row in figreg.items():30 s = ROOT/row["script"]31 if row["script"] and not s.exists(): problems.append(f"figures.csv: script introuvable {row['script']} ({key})")32 if "schema" not in (row.get("type_donnees") or "").lower() and "tikz" not in key.lower() and not (ROOT/"assets/figures"/key).exists(): problems.append(f"figures.csv: fichier inscrit mais absent {key}")33# 3. compteurs34n_fig = sum(len(incl.findall(t)) + t.count("\\begin{tikzpicture}") for t in texts.values())35n_tab = sum(t.count("\\begin{table}") + t.count("\\begin{longtable}") for t in texts.values())36n_eq = sum(t.count("\\begin{equation}") + t.count("\\begin{align}") + sum(ln.count("\\\\") for ln in re.findall(r"\\begin\{align\}(.*?)\\end\{align\}", t, re.S)) for t in texts.values())37n_ex = sum(t.count("\\begin{exercice}") for t in texts.values())38n_words = sum(len(re.sub(r"\\[a-zA-Z]+|[{}%$&_^~]", " ", t).split()) for p, t in texts.items() if p.name.startswith("ch"))39notes.append(f"figures≈{n_fig} tableaux≈{n_tab} équations≈{n_eq} exercices={n_ex} mots(chapitres)≈{n_words}")40# 4. log LaTeX41if a.log and not a.no_log:42 lp = ROOT/a.log43 if not lp.exists(): problems.append(f"log introuvable : {a.log}")44 else:45 L = lp.read_text(errors="ignore")46 for m in sorted(set(re.findall(r"LaTeX Warning: (Reference|Citation) `([^']+)' .*undefined", L))): problems.append(f"{m[0]} non définie : {m[1]}")47 if "There were undefined references" in L: problems.append("références non définies (voir log)")48 if re.search(r"^!", L, re.M): problems.append("erreur LaTeX fatale dans le log")49 m = re.findall(r"Output written on .*? \((\d+) pages", L); notes.append(f"pages PDF : {m[-1] if m else '?'}")50 notes.append(f"overfull hbox : {L.count('Overfull \\\\hbox')}")51# 5. CSV de vérification bien formés52for name, cols in {"verification/claims.csv": ["id"], "verification/calculations.csv": ["id"], "verification/figures.csv": ["fichier"], "sources/registry.csv": ["id"]}.items():53 f = ROOT/name54 if not f.exists(): problems.append(f"{name} manquant"); continue55 r = csv.DictReader(f.open(encoding="utf-8"))56 if not r.fieldnames or any(c not in r.fieldnames for c in cols): problems.append(f"{name}: en-tête invalide")57 else: notes.append(f"{name}: {sum(1 for _ in r)} lignes")58print("\n".join("• " + n for n in notes))59if problems:60 print(f"\n{len(problems)} problème(s) :"); print("\n".join(" ✗ " + p for p in problems[:80]))61 sys.exit(1 if (a.strict or a.log) else 0)62print("\n✓ aucun problème bloquant")63