#!/usr/bin/env python3 """Champ structuré `certifications` sur les fiches boutiques (vague 4). Deux gisements : 1. Les sources de découverte qui SONT des certifications/labels vérifiés (Aliments du Québec, Les Produits du Québec, Économusée, Arrêts gourmands) -> label dérivé de discovery_sources. 2. Le répertoire public CARTV/SIPAB des entreprises certifiées bio (data/raw/cartv_bio.jsonl, moissonné par wave4_discovery.py) -> appariement par domaine (fort) puis par nom normalisé unique (prudent), avec certificateur + date. Écrit dans la DB (stores.certifications, JSON) ET dans data/stores.json (champ `certifications`) — ⚠ sérialiser avec les autres écrivains du registre. Rejouable (fusion sans doublon par `label`). Usage : .venv/bin/python scripts/backfill_certifications.py """ import json import re import sys import unicodedata from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "scripts")) from fabrika import db # noqa: E402 from aggregate import norm_domain # noqa: E402 SOURCE_LABELS = { "alimentsduquebec_ent": ("Aliments du Québec", "Certification Aliments du Québec (adhérent vérifié)"), "lesproduitsduquebec_ent": ("Les Produits du Québec", "Certification Les Produits du Québec"), "artisansaloeuvre": ("Économusée", "Accréditation réseau Économusée / " "Artisans à l'œuvre"), "arretsgourmands": ("Arrêt gourmand certifié", "Arrêt gourmand certifié (Chaudière-Appalaches)"), } STOP = {"inc", "enr", "ltee", "les", "la", "le", "de", "du", "des", "et"} def norm_name(name): s = unicodedata.normalize("NFKD", name or "").encode("ascii", "ignore").decode().lower() return " ".join(t for t in re.findall(r"[a-z0-9]+", s) if t not in STOP) def main(): con = db.connect() stores = {r["id"]: dict(r) for r in con.execute( "SELECT id, name, discovery_sources, certifications FROM stores")} certs = {sid: {c["label"]: c for c in json.loads(s.get("certifications") or "[]")} for sid, s in stores.items()} # 1) labels dérivés des sources de découverte certifiantes n_src = 0 for sid, s in stores.items(): for src in json.loads(s.get("discovery_sources") or "[]"): if src in SOURCE_LABELS: label, detail = SOURCE_LABELS[src] if label not in certs[sid]: certs[sid][label] = {"label": label, "detail": detail, "source": src} n_src += 1 # 2) répertoire CARTV bio cartv_path = ROOT / "data" / "raw" / "cartv_bio.jsonl" n_dom = n_name = 0 if cartv_path.exists(): recs = [json.loads(l) for l in open(cartv_path)] by_dom = {} for r in recs: for w in r.get("websites", []): d = norm_domain(w) if d: by_dom.setdefault(d, r) # index nom normalisé -> fiches CARTV (pour l'appariement prudent) by_name = {} for r in recs: by_name.setdefault(norm_name(r["title"]), []).append(r) name_to_sids = {} for sid, s in stores.items(): name_to_sids.setdefault(norm_name(s["name"]), []).append(sid) def add_bio(sid, r, how): label = "Biologique — CARTV" if label in certs[sid]: return 0 certs[sid][label] = { "label": label, "detail": "Appellation biologique du Québec (répertoire public " "CARTV/SIPAB)", "certifier": r.get("certifier") or None, "since": r.get("cert_date") or None, "operations": r.get("operations") or [], "source": "cartv_bio", "match": how, } return 1 for sid in stores: if sid in by_dom: n_dom += add_bio(sid, by_dom[sid], "domaine") for nname, rs in by_name.items(): if len(rs) != 1 or not nname or len(nname) < 6: continue sids = name_to_sids.get(nname, []) if len(sids) == 1 and "Biologique — CARTV" not in certs[sids[0]]: n_name += add_bio(sids[0], rs[0], "nom_unique") # écriture DB n_stores = 0 for sid, cmap in certs.items(): if not cmap: continue con.execute("UPDATE stores SET certifications=? WHERE id=?", (json.dumps(list(cmap.values()), ensure_ascii=False), sid)) n_stores += 1 con.commit(); con.close() # écriture registre (champ informatif, la DB fait foi pour l'API) reg_path = ROOT / "data" / "stores.json" reg = json.load(open(reg_path)) for s in reg["stores"]: cmap = certs.get(s["id"]) if cmap: s["certifications"] = list(cmap.values()) json.dump(reg, open(reg_path, "w"), ensure_ascii=False, indent=1) print(f"certifications : {n_stores} boutiques porteuses " f"(+{n_src} labels de sources, +{n_dom} bio par domaine, " f"+{n_name} bio par nom unique)") if __name__ == "__main__": main()