Files

124 lines
4.3 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
patch_layouts_c5b.py — Chantier C5 lot 2 (catalogue : waterfall,
heatmap_table, funnel)
================================================================
Append sécurisé à layouts_v2.yaml (même mécanique verrouillée que C4 :
parse avant, contrôle du bloc layouts:, .bak, re-parse, restauration
auto). Après ce patch, propager : python3 prompt_injection_v2.py &&
python3 build_gallery.py.
Usage (dossier du pipeline, single-line) :
python3 patch_layouts_c5b.py
"""
import shutil
import sys
from pathlib import Path
import yaml
TARGET = Path("layouts_v2.yaml")
NEW_KEYS = ("waterfall", "heatmap_table", "funnel")
FRAGMENT = """
# ── Chantier C5 lot 2 — data & structure ──────────────────────────
waterfall:
id: L45
famille: Données
mode: light
champs: [titre, depart, marches, arrivee, unite, source]
champs_requis: [titre, depart, marches, arrivee]
agent_hint: >-
Pont de valeur (waterfall) : expliquer un écart entre deux
montants par des variations successives. depart et arrivee =
{label, valeur} ; marches = liste de {label, delta} SIGNÉ
(positif ou négatif, max 8). Le moteur calcule les cumuls —
ne jamais fournir de cumul. Idéal pour : évolution de budget,
pont d'effectifs, décomposition d'un résultat.
heatmap_table:
id: L46
famille: Comparaison
mode: light
champs: [titre, headers, rows, legende]
champs_requis: [titre, headers, rows]
agent_hint: >-
Tableau à intensité : évaluer plusieurs items sur plusieurs
critères. headers = colonnes (max 6) ; rows = {label,
scores} avec score ENTIER de 0 (faible) à 4 (fort), max
8 lignes. Le moteur traduit chaque score en teinte de navy —
aucune autre donnée. Idéal pour : maturité, couverture
fonctionnelle, cartographie de risques.
funnel:
id: L47
famille: Process
mode: light
champs: [titre, etapes, source]
champs_requis: [titre, etapes]
agent_hint: >-
Entonnoir de conversion : volumes décroissants d'étape en
étape. etapes = {label, valeur, description?} du haut vers le
bas, 3 à 5 étages. Largeurs proportionnelles aux valeurs
(plancher de lisibilité), dernier étage corail. Idéal pour :
pipeline commercial, adoption, qualification progressive.
"""
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 NEW_KEYS:
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-c5b")
print(" + Sauvegarde : %s.bak-c5b" % 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 NEW_KEYS)
assert len(nl) == len(layouts) + len(NEW_KEYS)
except Exception as e:
shutil.copy2(str(TARGET) + ".bak-c5b", TARGET)
fail("Contrôle post-append échoué (%s) — fichier restauré." % e)
print(" + waterfall (L45), heatmap_table (L46), funnel (L47) "
"ajoutés (%d layouts au total)." % (len(layouts)
+ len(NEW_KEYS)))
print("\n Propager : python3 prompt_injection_v2.py && python3 "
"build_gallery.py")
if __name__ == "__main__":
main()