#!/usr/bin/env python3 """Chapitre 10 — figures Matplotlib : ch10-fig-chronologie-cadres.pdf chronologie documentaire des cadres normatifs (dates du registre C) ; ch10-fig-matrice-normes.pdf statistiques descriptives de la matrice des dispositions (sources/normes-C.csv) ; ch10-fig-grille-controle.pdf grille de détection des douze éléments dans quatre extraits FICTIFS. Usage (racine du dépôt) : PYTHONPATH=analysis/python python3 analysis/python/figures/ch10_figures.py""" from __future__ import annotations import sys, pathlib, datetime as dt import matplotlib.pyplot as plt import matplotlib.dates as mdates from matplotlib.lines import Line2D from matplotlib.patches import Patch sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1])) sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1] / "cases")) from traite.style import setup, palette, savefig, LARGEUR import ch10_matrice_normes as mn import ch10_controle_rapport as cr def fig_chronologie(ch): fig, ax = plt.subplots(figsize=(LARGEUR, 4.0)) ch = ch.copy() ch["ordre"] = ch["effet"].rank(method="first") ch = ch.sort_values("effet") col = {"en_vigueur": palette.cat[0], "remplace": palette.ink2, "projet": palette.cat[1], "reference": palette.cat[2]} fin_axe = dt.date(2028, 6, 30) labels = [] for i, (_, r) in enumerate(ch.iterrows()): c = col[r["statut"]] fin = r["fin"] if r["fin"] is not None else (r["effet"] if r["statut"] == "projet" else fin_axe) ls = ":" if r["statut"] == "projet" else "-" if r["statut"] != "projet": ax.plot([r["effet"], fin], [i, i], color=c, lw=2.2, solid_capstyle="butt", alpha=0.9 if r["statut"] != "remplace" else 0.6) if r["publication"] is not None: ax.plot([r["publication"], r["effet"]], [i, i], color=c, lw=1.0, ls=ls) ax.plot(r["publication"], i, marker="o", mfc="white", mec=c, ms=5.5, mew=1.3) ax.plot(r["effet"], i, marker="o", color=c, ms=5.5) if r["statut"] == "projet": ax.plot(r["effet"], i, marker="o", mfc="white", mec=c, ms=5.5, mew=1.3) if r["fin"] is not None: ax.plot(r["fin"], i, marker="|", color=c, ms=9, mew=1.6) labels.append(r["cadre"] + (" *" if r.get("verification") == "partielle" else "")) ax.axvline(mn.DATE_REF, color=palette.ink2, lw=0.8, ls="--") ax.text(mn.DATE_REF, len(ch) - 0.4, " 31 oct. 2026", fontsize=7.5, color=palette.ink2, va="bottom") ax.set_yticks(range(len(ch))); ax.set_yticklabels(labels, fontsize=8) ax.tick_params(axis="y", length=0) ax.set_ylim(-0.7, len(ch) - 0.3) ax.set_xlim(dt.date(2012, 6, 1), fin_axe) ax.xaxis.set_major_locator(mdates.YearLocator(2)); ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y")) ax.grid(axis="y", visible=False) for s in ("left",): ax.spines[s].set_visible(False) h = [Line2D([0], [0], color=col["en_vigueur"], lw=2.2, label="en vigueur au 31 oct. 2026"), Line2D([0], [0], color=col["remplace"], lw=2.2, alpha=0.6, label="remplacé"), Line2D([0], [0], color=col["reference"], lw=2.2, label="norme de référence (IAAO)"), Line2D([0], [0], color=col["projet"], lw=1.0, ls=":", marker="o", mfc="white", label="projet (date proposée)"), Line2D([0], [0], color=palette.ink2, lw=0, marker="o", mfc="white", mec=palette.ink2, label="publication → entrée en vigueur")] ax.legend(handles=h, loc="upper center", bbox_to_anchor=(0.5, -0.14), fontsize=7.5, ncol=3, frameon=False) ax.set_xlabel("Année") savefig(fig, "ch10/ch10-fig-chronologie-cadres.pdf") def fig_matrice(st): fig, (a1, a2) = plt.subplots(1, 2, figsize=(LARGEUR, 2.9), gridspec_kw={"width_ratios": [1.15, 1]}) fam = st["par_famille"].sort_values() couleurs = [palette.cat[0] if f.startswith("OEAQ") else palette.ink2 for f in fam.index] a1.barh(fam.index, fam.values, color=couleurs, height=0.62) for y, v in enumerate(fam.values): a1.text(v + 0.6, y, str(v), va="center", fontsize=8, color=palette.ink) a1.set_xlabel("Dispositions (n = %d)" % st["n"]); a1.set_xlim(0, fam.max() * 1.18) a1.set_title("Par famille de sources", loc="left", fontsize=9) a1.grid(axis="y", visible=False) top = st["par_chapitre"].head(10).sort_values() couleurs2 = [palette.cat[0] if c == "ch10" else palette.ink2 for c in top.index] a2.barh([c.replace("ch", "ch. ") for c in top.index], top.values, color=couleurs2, height=0.62) for y, v in enumerate(top.values): a2.text(v + 0.6, y, str(v), va="center", fontsize=8, color=palette.ink) a2.set_xlabel("Renvois vers le chapitre"); a2.set_xlim(0, top.max() * 1.18) a2.set_title("Dix chapitres les plus visés", loc="left", fontsize=9) a2.grid(axis="y", visible=False) fig.tight_layout(w_pad=2.0) savefig(fig, "ch10/ch10-fig-matrice-normes.pdf") LIBELLES_COURTS = {1: "Identification physique et légale", 2: "Droits de propriété évalués", 3: "But et fin de l'évaluation", 4: "Définition de la valeur", 5: "Date d'évaluation", 6: "Étendue du travail", 7: "Réserves et hypothèses", 8: "Information et raisonnement", 9: "Usage le meilleur et le plus profitable", 10: "Rejet d'une méthode", 11: "Conformité et écarts aux directives", 12: "Attestation"} def fig_grille(out): extraits = list(cr.EXTRAITS.keys()) fig, ax = plt.subplots(figsize=(LARGEUR, 3.4)) for j, e in enumerate(cr.ELEMENTS): for i, nom in enumerate(extraits): strict = out["controle"][(nom, "strict")][e.cle][0] souple = out["controle"][(nom, "souple")][e.cle][0] if strict: ax.scatter(i, j, s=120, marker="s", color=palette.cat[0], zorder=3) elif souple: ax.scatter(i, j, s=120, marker="s", facecolor="white", edgecolor=palette.cat[1], linewidth=1.6, zorder=3) else: ax.scatter(i, j, s=40, marker="x", color=palette.ink2, linewidth=1.0, zorder=3) ax.set_xticks(range(len(extraits))) ax.set_xticklabels([f"Extrait {n}\n{out['taux'][(n,'strict')]*12:.0f}/12 (strict)" for n in extraits], fontsize=8) ax.set_yticks(range(len(cr.ELEMENTS))) ax.set_yticklabels([f"{e.no}. {LIBELLES_COURTS[e.no]}" for e in cr.ELEMENTS], fontsize=7.5) ax.invert_yaxis(); ax.set_xlim(-0.6, len(extraits) - 0.4) ax.grid(False) for s in ("left", "bottom"): ax.spines[s].set_visible(False) ax.tick_params(length=0) h = [Line2D([0], [0], marker="s", color=palette.cat[0], lw=0, ms=8, label="détecté (lexique strict)"), Line2D([0], [0], marker="s", mfc="white", mec=palette.cat[1], mew=1.6, lw=0, ms=8, label="détecté seulement par le lexique souple"), Line2D([0], [0], marker="x", color=palette.ink2, lw=0, ms=6, label="non détecté")] ax.legend(handles=h, loc="upper center", bbox_to_anchor=(0.5, -0.16), ncol=3, fontsize=7.5, frameon=False) savefig(fig, "ch10/ch10-fig-grille-controle.pdf") def main(): setup() res = mn.main(verbose=False) out = cr.main(verbose=False) fig_chronologie(res["chronologie"]) fig_matrice(res["stats"]) fig_grille(out) print("figures du chapitre 10 écrites dans assets/figures/ch10/") if __name__ == "__main__": main()