#!/usr/bin/env python3 """Fusionne sources/registry-*.csv → sources/registry.csv, sources/normes-*.csv → sources/normes-matrice.csv, references/lib-*.bib → references/library.bib (clés dédoublonnées, la première occurrence gagne ; library.bib hérité d'abord). Idempotent : les fichiers fusionnés sont reconstruits à partir des fichiers sources + de la partie « héritée » de library.bib.""" import csv, re, pathlib, sys ROOT = pathlib.Path(__file__).resolve().parents[1] # --- registre hdr = "id,type,titre,organisme,version_ou_date,url,date_consultation,statut,utilise_dans,notes".split(",") rows = [] for f in sorted((ROOT / "sources").glob("registry-*.csv")): with f.open(encoding="utf-8") as fh: r = csv.DictReader(fh) for row in r: rows.append({k: (row.get(k) or "").strip() for k in hdr}) seen = set(); out = [] for row in rows: if row["id"] in seen or not row["id"]: continue seen.add(row["id"]); out.append(row) with (ROOT / "sources/registry.csv").open("w", encoding="utf-8", newline="") as fh: w = csv.DictWriter(fh, fieldnames=hdr); w.writeheader(); w.writerows(out) print(f"registry.csv : {len(out)} sources ({sum(1 for r in out if r['statut']=='verifie')} vérifiées)") # --- normes hdrn = "id,domaine,disposition,texte_source,reference_precise,effet_sur_evaluation,version_verifiee,date_consultation,chapitres".split(",") nrows = [] for f in sorted((ROOT / "sources").glob("normes-*.csv")): if f.name == "normes-matrice.csv": continue with f.open(encoding="utf-8") as fh: for row in csv.DictReader(fh): nrows.append({k: (row.get(k) or "").strip() for k in hdrn}) with (ROOT / "sources/normes-matrice.csv").open("w", encoding="utf-8", newline="") as fh: w = csv.DictWriter(fh, fieldnames=hdrn); w.writeheader(); w.writerows(nrows) print(f"normes-matrice.csv : {len(nrows)} dispositions") # --- bib lib = ROOT / "references/library.bib" txt = lib.read_text(encoding="utf-8") marker = "\n% ===== ENTRÉES VÉRIFIÉES (phase 1, fusion automatique de lib-*.bib) =====\n" base = txt.split(marker)[0].rstrip() + "\n" def keys(s): return re.findall(r"^@\w+\s*\{\s*([^,\s]+)\s*,", s, re.M) have = set(keys(base)); added = []; dup = [] for f in sorted((ROOT / "references").glob("lib-*.bib")): s = f.read_text(encoding="utf-8") # découpe en entrées parts = re.split(r"(?m)^(?=@\w+\s*\{)", s) for p in parts: m = re.match(r"@\w+\s*\{\s*([^,\s]+)\s*,", p) if not m: continue k = m.group(1) if k in have: dup.append(f"{f.name}:{k}"); continue have.add(k); added.append(p.strip() + "\n") lib.write_text(base + marker + "\n".join(added), encoding="utf-8") print(f"library.bib : {len(have)} clés ({len(added)} ajoutées ; doublons ignorés : {len(dup)})") if dup: print(" doublons :", ", ".join(dup[:20])) # --- fichiers par chapitre (verification/chapters/chNN-*.csv, references/lib-chNN.bib) → fichiers globaux def merge_csv(pattern, target, keycol): tgt = ROOT / target with tgt.open(encoding="utf-8") as fh: r = csv.DictReader(fh); cols = r.fieldnames; rows = list(r) index = {row[keycol]: i for i, row in enumerate(rows)} n = 0 for f in sorted((ROOT / "verification/chapters").glob(pattern)): with f.open(encoding="utf-8") as fh: for row in csv.DictReader(fh): row = {c: (row.get(c) or "").strip() for c in cols} if not row[keycol]: continue if row[keycol] in index: rows[index[row[keycol]]] = row # la version du chapitre fait foi else: index[row[keycol]] = len(rows); rows.append(row); n += 1 with tgt.open("w", encoding="utf-8", newline="") as fh: w = csv.DictWriter(fh, fieldnames=cols); w.writeheader(); w.writerows(rows) print(f"{target} : {len(rows)} lignes (+{n})") merge_csv("*-figures.csv", "verification/figures.csv", "fichier") merge_csv("*-calculations.csv", "verification/calculations.csv", "id") merge_csv("*-claims.csv", "verification/claims.csv", "id") # registre par chapitre → registry.csv (append sans doublon d'id) reg = ROOT / "sources/registry.csv" with reg.open(encoding="utf-8") as fh: r = csv.DictReader(fh); rrows = list(r) ids = {x["id"] for x in rrows}; n = 0 for f in sorted((ROOT / "verification/chapters").glob("*-registry.csv")): with f.open(encoding="utf-8") as fh: for row in csv.DictReader(fh): row = {c: (row.get(c) or "").strip() for c in hdr} if row["id"] and row["id"] not in ids: rrows.append(row); ids.add(row["id"]); n += 1 with reg.open("w", encoding="utf-8", newline="") as fh: w = csv.DictWriter(fh, fieldnames=hdr); w.writeheader(); w.writerows(rrows) print(f"registry.csv (+chapitres) : {len(rrows)} (+{n})") # bib par chapitre txt = lib.read_text(encoding="utf-8"); have = set(keys(txt)); added = [] for f in sorted((ROOT / "references").glob("lib-ch*.bib")) + sorted((ROOT / "references").glob("lib-app*.bib")): for p in re.split(r"(?m)^(?=@\w+\s*\{)", f.read_text(encoding="utf-8")): m = re.match(r"@\w+\s*\{\s*([^,\s]+)\s*,", p) if m and m.group(1) not in have: have.add(m.group(1)); added.append(p.strip() + "\n") if added: lib.write_text(txt.rstrip() + "\n\n% ===== ENTRÉES AJOUTÉES PAR LES CHAPITRES =====\n" + "\n".join(added), encoding="utf-8") print(f"library.bib (+chapitres) : {len(have)} clés (+{len(added)})")