121 lines
4.2 KiB
Python
121 lines
4.2 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""
|
|||
|
|
patch_layouts_c5a.py — Chantier C5 · lot 1 (catalogue : charts natifs)
|
|||
|
|
======================================================================
|
|||
|
|
Ajoute bar_chart (L42), line_chart (L43) et donut_split (L44) à
|
|||
|
|
layouts_v2.yaml par APPEND sécurisé (même mécanique que le patch C4 :
|
|||
|
|
parse avant, contrôle de fin de bloc, backup, re-parse, restauration
|
|||
|
|
automatique en cas d'échec — le fichier n'est jamais reformaté).
|
|||
|
|
|
|||
|
|
Après ce patch, propager : python3 prompt_injection_v2.py && python3
|
|||
|
|
build_gallery.py
|
|||
|
|
|
|||
|
|
Usage (dossier du pipeline, single-line) :
|
|||
|
|
python3 patch_layouts_c5a.py
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import shutil
|
|||
|
|
import sys
|
|||
|
|
from pathlib import Path
|
|||
|
|
|
|||
|
|
import yaml
|
|||
|
|
|
|||
|
|
TARGET = Path("layouts_v2.yaml")
|
|||
|
|
|
|||
|
|
FRAGMENT = """
|
|||
|
|
# ── Chantier C5 lot 1 — charts natifs (éditables dans PowerPoint) ──
|
|||
|
|
bar_chart:
|
|||
|
|
id: L42
|
|||
|
|
famille: Données
|
|||
|
|
mode: light
|
|||
|
|
champs: [titre, categories, series, unite, source, horizontal]
|
|||
|
|
champs_requis: [titre, categories, series]
|
|||
|
|
agent_hint: >-
|
|||
|
|
Comparaison de valeurs par catégories — graphique NATIF éditable.
|
|||
|
|
Max 8 catégories × 3 séries. series = liste de {label, values} ;
|
|||
|
|
values = nombres SANS guillemets, alignés sur categories.
|
|||
|
|
horizontal: true pour des barres (libellés longs). unite (ex M€)
|
|||
|
|
et source optionnels. Couleurs imposées : navy, coral, glacier.
|
|||
|
|
|
|||
|
|
line_chart:
|
|||
|
|
id: L43
|
|||
|
|
famille: Données
|
|||
|
|
mode: light
|
|||
|
|
champs: [titre, points_x, series, unite, source]
|
|||
|
|
champs_requis: [titre, points_x, series]
|
|||
|
|
agent_hint: >-
|
|||
|
|
Évolution temporelle — graphique NATIF éditable. Max 12 points ×
|
|||
|
|
3 séries. points_x = libellés d'axe (mois, années...) ; series =
|
|||
|
|
{label, values}, nombres sans guillemets. Le dernier point de la
|
|||
|
|
première série est automatiquement mis en valeur (corail).
|
|||
|
|
|
|||
|
|
donut_split:
|
|||
|
|
id: L44
|
|||
|
|
famille: Données
|
|||
|
|
mode: light
|
|||
|
|
champs: [titre, segments, valeur_centrale, source]
|
|||
|
|
champs_requis: [titre, segments]
|
|||
|
|
agent_hint: >-
|
|||
|
|
Répartition d'un tout : anneau à gauche + légende détaillée à
|
|||
|
|
droite. 2 à 6 segments = {label, valeur} (nombres sans
|
|||
|
|
guillemets). valeur_centrale (optionnelle) s'affiche au centre de
|
|||
|
|
l'anneau (ex : total « 120 M€ »). Couleurs = cycle PR imposé.
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
|
|||
|
|
def fail(msg):
|
|||
|
|
print(" ! %s" % msg)
|
|||
|
|
sys.exit(1)
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
if not TARGET.exists():
|
|||
|
|
fail("%s introuvable — lancer depuis le dossier du pipeline."
|
|||
|
|
% TARGET)
|
|||
|
|
raw = TARGET.read_text(encoding="utf-8")
|
|||
|
|
try:
|
|||
|
|
data = yaml.safe_load(raw)
|
|||
|
|
except yaml.YAMLError as e:
|
|||
|
|
fail("layouts_v2.yaml actuel illisible : %s" % e)
|
|||
|
|
layouts = (data or {}).get("layouts")
|
|||
|
|
if not isinstance(layouts, dict):
|
|||
|
|
fail("Bloc layouts: introuvable dans le fichier.")
|
|||
|
|
for key in ("bar_chart", "line_chart", "donut_split"):
|
|||
|
|
if key in layouts:
|
|||
|
|
fail("'%s' déjà présent — rien à faire." % key)
|
|||
|
|
|
|||
|
|
last = next((l for l in reversed(raw.splitlines())
|
|||
|
|
if l.strip() and not l.strip().startswith("#")), "")
|
|||
|
|
if not last.startswith(" "):
|
|||
|
|
fail("Le fichier ne se termine pas dans le bloc layouts: "
|
|||
|
|
"(dernière ligne non indentée : %r) — fusion manuelle "
|
|||
|
|
"requise." % last[:40])
|
|||
|
|
|
|||
|
|
shutil.copy2(TARGET, str(TARGET) + ".bak-c5a")
|
|||
|
|
print(" + Sauvegarde : %s.bak-c5a" % TARGET)
|
|||
|
|
|
|||
|
|
with open(TARGET, "a", encoding="utf-8") as f:
|
|||
|
|
if not raw.endswith("\n"):
|
|||
|
|
f.write("\n")
|
|||
|
|
f.write(FRAGMENT)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
check = yaml.safe_load(TARGET.read_text(encoding="utf-8"))
|
|||
|
|
nl = check["layouts"]
|
|||
|
|
assert all(k in nl for k in ("bar_chart", "line_chart",
|
|||
|
|
"donut_split"))
|
|||
|
|
assert len(nl) == len(layouts) + 3
|
|||
|
|
except Exception as e:
|
|||
|
|
shutil.copy2(str(TARGET) + ".bak-c5a", TARGET)
|
|||
|
|
fail("Contrôle post-append échoué (%s) — fichier restauré." % e)
|
|||
|
|
|
|||
|
|
print(" + bar_chart (L42), line_chart (L43), donut_split (L44) "
|
|||
|
|
"ajoutés (%d layouts au total)." % (len(layouts) + 3))
|
|||
|
|
print("\n Propager : python3 prompt_injection_v2.py && python3 "
|
|||
|
|
"build_gallery.py")
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|