Files
cesizen-individuel/generate_diagrams_bloc3.py

220 lines
10 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Génère les schémas du dossier Bloc 3 (Déployer et sécuriser) — CESIZen.
Sortie : ./assets_bloc3/*.png (réutilisés dans le .docx et le .pptx)
"""
import os
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
import matplotlib.font_manager as fm
# ─── Charte CESIZen ───────────────────────────────────────────────
GREEN = "#06C656"
GREEN_D = "#049641"
BLUE = "#004682"
BLUE_L = "#E6F0FA"
GREY_D = "#232323"
GREY = "#6E6E6E"
WHITE = "#FFFFFF"
INK = "#23232D"
RED = "#C8102E"
ORANGE = "#E4801C"
YELLOW_A = "#F0C808"
HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, "assets_bloc3")
os.makedirs(OUT, exist_ok=True)
plt.rcParams["font.family"] = "DejaVu Sans"
def box(ax, x, y, w, h, text, fc, ec=None, tc="white", fs=10, bold=True, radius=0.02):
ec = ec or fc
p = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad=0.005,rounding_size={radius}",
fc=fc, ec=ec, lw=1.4, mutation_aspect=1)
ax.add_patch(p)
ax.text(x + w / 2, y + h / 2, text, ha="center", va="center",
color=tc, fontsize=fs, fontweight="bold" if bold else "normal", zorder=5)
def arrow(ax, x1, y1, x2, y2, color=GREY_D, style="-|>", lw=1.8, ls="-"):
a = FancyArrowPatch((x1, y1), (x2, y2), arrowstyle=style, mutation_scale=16,
color=color, lw=lw, linestyle=ls, zorder=1)
ax.add_patch(a)
# ══════════════════════════════════════════════════════════════════
# 1. ARCHITECTURE DE DÉPLOIEMENT + CHAÎNE CI/CD
# ══════════════════════════════════════════════════════════════════
def diagram_deploiement():
fig, ax = plt.subplots(figsize=(12, 6.6))
ax.set_xlim(0, 12); ax.set_ylim(0, 6.6); ax.axis("off")
ax.text(6, 6.35, "Chaîne de déploiement CESIZen — du poste développeur à la production",
ha="center", fontsize=13, fontweight="bold", color=BLUE)
# --- Bandeau des 3 environnements ---
envs = [
(0.3, "DÉVELOPPEMENT", "Poste local", GREEN,
["Docker Compose (dev)", "PostgreSQL 16 + Redis 7", "Hot-reload back & front", "APP_DEBUG=true"]),
(4.15, "TEST / INTÉGRATION", "Gitea Actions (CI)", ORANGE,
["Runner conteneurisé", "Base SQLite en mémoire", "29 tests automatisés", "Lint (Pint / ESLint)"]),
(8.0, "PRODUCTION", "Coolify (PaaS)", BLUE,
["Docker Compose (prod)", "HTTPS / TLS (reverse proxy)", "Secrets générés hors dépôt", "Health checks + restart"]),
]
for x, titre, sous, col, items in envs:
box(ax, x, 3.4, 3.55, 2.2, "", BLUE_L, ec=col, tc=INK)
box(ax, x, 5.0, 3.55, 0.6, titre, col, fs=11)
ax.text(x + 1.775, 4.75, sous, ha="center", fontsize=9.5, style="italic", color=GREY_D)
for i, it in enumerate(items):
ax.text(x + 0.15, 4.45 - i * 0.27, "• " + it, ha="left", fontsize=8.5, color=INK)
arrow(ax, 3.95, 4.5, 4.1, 4.5, color=GREY_D)
arrow(ax, 7.8, 4.5, 7.95, 4.5, color=GREY_D)
# --- Chaîne CI/CD (bas) ---
ax.text(6, 2.9, "Flux d'intégration & de livraison continues", ha="center",
fontsize=11, fontweight="bold", color=GREEN_D)
steps = [
(0.3, "git push\n(Gitea)", GREY_D),
(2.55, "Gitea Actions\ndéclenché", ORANGE),
(4.8, "Build + Tests\n(29 tests)", ORANGE),
(7.05, "Webhook\nCoolify", BLUE),
(9.3, "Build images\n+ déploiement", BLUE),
]
for x, t, col in steps:
box(ax, x, 1.3, 2.05, 1.05, t, col, fs=9.5)
for i in range(len(steps) - 1):
arrow(ax, steps[i][0] + 2.05, 1.82, steps[i + 1][0], 1.82, color=GREEN_D, lw=2)
box(ax, 9.3, 0.15, 2.05, 0.8, "cesizen.cleanows.fr\napi.cesizen.cleanows.fr", GREEN, fs=8)
arrow(ax, 10.32, 1.3, 10.32, 0.95, color=GREEN_D, lw=2)
ax.text(0.3, 0.55, "Rollback : redéploiement de la version\nGit précédente (tag) en 1 clic Coolify.",
ha="left", fontsize=8, color=GREY, style="italic")
plt.tight_layout()
fig.savefig(os.path.join(OUT, "archi_deploiement.png"), dpi=170, bbox_inches="tight")
plt.close(fig)
# ══════════════════════════════════════════════════════════════════
# 2. MATRICE DE RISQUES (criticité = gravité × probabilité)
# ══════════════════════════════════════════════════════════════════
def diagram_matrice_risques():
fig, ax = plt.subplots(figsize=(11, 6.8))
n = 5
for g in range(n): # gravité (y)
for p in range(n): # probabilité (x)
crit = (g + 1) * (p + 1)
if crit <= 4:
c = "#2E9E4F"
elif crit <= 9:
c = "#F0C808"
elif crit <= 14:
c = "#E4801C"
else:
c = "#C8102E"
ax.add_patch(plt.Rectangle((p, g), 1, 1, fc=c, ec="white", lw=2, alpha=0.9))
ax.text(p + 0.5, g + 0.5, str(crit), ha="center", va="center",
fontsize=9, color="white", fontweight="bold", alpha=0.6)
# Positionnement des risques réels du projet (P sur 1..5, G sur 1..5)
risques = [
("R1", 2, 4), # Fuite données de santé (Art.9)
("R2", 3, 3), # Injection / XSS
("R3", 2, 3), # Compromission compte admin
("R4", 3, 2), # DDoS / indisponibilité
("R5", 2, 2), # Perte de données (sauvegarde)
("R6", 4, 2), # Dépendances vulnérables
("R7", 3, 4), # Absence de chiffrement au repos
("R8", 2, 1), # Erreur humaine déploiement
]
for nom, p, g in risques:
ax.plot(p - 0.5, g - 0.5, "o", ms=22, mfc="white", mec=INK, mew=1.8, zorder=6)
ax.text(p - 0.5, g - 0.5, nom, ha="center", va="center", fontsize=8.5,
fontweight="bold", color=INK, zorder=7)
ax.set_xlim(0, n); ax.set_ylim(0, n)
ax.set_xticks([i + 0.5 for i in range(n)])
ax.set_yticks([i + 0.5 for i in range(n)])
ax.set_xticklabels(["Très faible", "Faible", "Moyenne", "Forte", "Très forte"], fontsize=9)
ax.set_yticklabels(["Mineure", "Modérée", "Grave", "Critique", "Catastroph."], fontsize=9)
ax.set_xlabel("Probabilité →", fontsize=11, fontweight="bold", color=BLUE)
ax.set_ylabel("Gravité →", fontsize=11, fontweight="bold", color=BLUE)
ax.set_title("Matrice de criticité des risques CESIZen (criticité = gravité × probabilité)",
fontsize=12.5, fontweight="bold", color=BLUE, pad=12)
for s in ax.spines.values():
s.set_visible(False)
ax.tick_params(length=0)
legende = ("R1 Fuite de données de santé R2 Injection SQL/XSS R3 Compromission compte admin "
"R4 DDoS / indisponibilité\nR5 Perte de données R6 Dépendances vulnérables "
"R7 Absence de chiffrement au repos R8 Erreur humaine de déploiement")
fig.text(0.5, -0.02, legende, ha="center", fontsize=8.3, color=GREY_D)
plt.tight_layout()
fig.savefig(os.path.join(OUT, "matrice_risques.png"), dpi=170, bbox_inches="tight")
plt.close(fig)
# ══════════════════════════════════════════════════════════════════
# 3. CYCLE DE VIE D'UN TICKET (maintenance corrective)
# ══════════════════════════════════════════════════════════════════
def diagram_ticket():
fig, ax = plt.subplots(figsize=(12, 5.2))
ax.set_xlim(0, 12); ax.set_ylim(0, 5.2); ax.axis("off")
ax.text(6, 4.95, "Cycle de vie d'un ticket sur Gitea (prestataire ↔ Ministère)",
ha="center", fontsize=13, fontweight="bold", color=BLUE)
etapes = [
("1. Signalement\nIssue créée\n(gabarit incident)", GREY_D),
("2. Qualification\nLabels : gravité,\npriorité, module", BLUE),
("3. Priorisation\nSLA appliqué\n(bloquant/majeur/mineur)", ORANGE),
("4. Correction\nbranche fix/*\n+ tests", GREEN_D),
("5. Revue & CI\nPull Request\n29 tests verts", GREEN_D),
("6. Livraison\nmerge → Coolify\ndéploie", GREEN),
("7. Clôture\nvalidation client\nIssue fermée", GREEN),
]
x = 0.15
w = 1.58
gap = 0.11
y = 2.5
for i, (t, col) in enumerate(etapes):
box(ax, x, y, w, 1.3, t, col, fs=8.3)
if i < len(etapes) - 1:
arrow(ax, x + w, y + 0.65, x + w + gap, y + 0.65, color=GREY_D, lw=1.6)
x += w + gap
# Bandeau SLA
ax.text(6, 1.9, "Délais contractuels (jours ouvrés) — repris du cahier des charges",
ha="center", fontsize=10, fontweight="bold", color=GREEN_D)
sla = [
("Bloquant critique", "Prise en compte 1 h", "Correction 3 h", RED),
("Bloquant fort", "Prise en compte 2 h", "Correction 6 h", ORANGE),
("Majeur", "Prise en compte 7 h", "Correction 16 h", YELLOW_A),
("Mineur (par lots)", "Prise en compte 1 j", "Correction 40 h", GREEN),
]
bx = 0.3
bw = 2.85
for titre, d1, d2, col in sla:
box(ax, bx, 0.35, bw, 1.15, "", BLUE_L, ec=col, tc=INK)
box(ax, bx, 1.12, bw, 0.38, titre, col, fs=9, tc="white")
ax.text(bx + bw / 2, 0.92, d1, ha="center", fontsize=8.3, color=INK)
ax.text(bx + bw / 2, 0.62, d2, ha="center", fontsize=8.3, color=INK)
bx += bw + 0.1
plt.tight_layout()
fig.savefig(os.path.join(OUT, "cycle_ticket.png"), dpi=170, bbox_inches="tight")
plt.close(fig)
if __name__ == "__main__":
diagram_deploiement()
diagram_matrice_risques()
diagram_ticket()
print("Schémas Bloc 3 générés dans :", OUT)
for f in sorted(os.listdir(OUT)):
print(" -", f)