Files
sliding-automation/patches/patch_render_engine_c5c.py

198 lines
7.8 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
patch_render_engine_c5c.py — Chantier C5 · lot 3 (agenda, pyramid)
==================================================================
Patch strict de render_engine_v2.py. Réintroduit les deux layouts v1
disparus à la refonte, restylés PR Editorial. Aucun prérequis sur les
lots 1/2 (aucun helper chart utilisé).
agenda (L40) — sommaire typographique aéré : badge numéroté (navy ;
corail si actif: true), label (bold navy si actif), durée
alignée à droite en muted, filet séparateur card_alt.
Bornes : 2-8 sections (troncature tracée au-delà).
pyramid (L41) — pyramide à degrés : rectangles empilés centrés,
largeurs FIXES par étage (jamais recalculées) :
3 niveaux : 44 / 72 / 100 %
4 niveaux : 40 / 60 / 80 / 100 %
Couleurs fixes du sommet à la base : navy, navy_light,
slate, glacier (texte blanc, navy sur glacier). Descriptions
optionnelles alignées à droite de chaque étage (la pyramide
passe alors à 58 % de largeur) ; sans description, pyramide
centrée sur 80 %.
Usage (dossier du pipeline, single-line) :
python3 patch_render_engine_c5c.py
Vérifie chaque ancre, écrit .bak-c5c, compile, idempotent.
"""
import py_compile
import shutil
import sys
from pathlib import Path
TARGET = Path("render_engine_v2.py")
COMPONENTS = ''' # ---------------- structure & concept (C5 lot 3) ----------------
AGENDA_MAX = 8
PYRAMID_WIDTHS = {3: (0.44, 0.72, 1.0),
4: (0.40, 0.60, 0.80, 1.0)}
def _render_agenda(self, slide, d):
"""Sommaire : badges numérotés, section active en corail."""
self._title(slide, pick(d, "titre", "title"))
sections = (d.get("sections") or [])[:self.AGENDA_MAX]
if len(d.get("sections") or []) > self.AGENDA_MAX:
print(f" ~ agenda : sections tronquées à {self.AGENDA_MAX}")
if len(sections) < 2:
self._text(slide, self.MX, self.CONT_Y, 12, 1.0,
"[agenda : 2 sections minimum]", size=14,
color=self.C["muted"])
return
n = len(sections)
gap = 0.5
row_h = min(1.9, (self.CONT_H - gap * (n - 1)) / n)
tot = n * row_h + (n - 1) * gap
y = self._cy(tot)
bd = self.components["badge"]["sizes"]["m"]
x0 = self.MX + 1.5
w = self.SLIDE_W - 2 * self.MX - 3.0
for i, s in enumerate(sections):
if not isinstance(s, dict):
s = {"label": str(s)}
actif = bool(s.get("actif"))
num = s.get("numero", i + 1)
fill = self.C["coral"] if actif else self.C["navy"]
self._badge(slide, x0 + bd / 2, y + row_h / 2, bd, num,
fill=fill, font_size=20)
self._text(slide, x0 + bd + 1.0, y, w - bd - 6.0, row_h,
as_label(s, "label", "titre"),
size=20 if actif else 19, bold=actif,
color=self.C["navy"] if actif
else self.C["body"],
anchor=MSO_ANCHOR.MIDDLE)
duree = pick(s, "duree", "duration")
if duree:
self._text(slide, x0 + w - 4.5, y, 4.5, row_h, duree,
size=13, italic=True, color=self.C["muted"],
align=PP_ALIGN.RIGHT,
anchor=MSO_ANCHOR.MIDDLE, fit=False)
if i < n - 1:
self._rect(slide, x0 + bd + 1.0,
y + row_h + gap / 2 - 0.015,
w - bd - 1.0, 0.03, self.C["card"])
y += row_h + gap
def _render_pyramid(self, slide, d):
"""Pyramide à degrés : largeurs fixes, couleurs fixes."""
self._title(slide, pick(d, "titre", "title"))
niveaux = (d.get("niveaux") or d.get("levels") or [])[:4]
if len(niveaux) < 3:
self._text(slide, self.MX, self.CONT_Y, 12, 1.0,
"[pyramid : 3 ou 4 niveaux]", size=14,
color=self.C["muted"])
return
n = len(niveaux)
widths = self.PYRAMID_WIDTHS[n]
colors = [self.C["navy"], self.C["navy2"],
self.C["slate"], self.C["glacier"]]
if n == 3:
colors = [self.C["navy"], self.C["navy2"],
self.C["glacier"]]
has_desc = any(isinstance(nv, dict) and pick(nv, "description")
for nv in niveaux)
zone_w = self.SLIDE_W - 2 * self.MX
pyr_w = zone_w * (0.58 if has_desc else 0.80)
pyr_x = self.MX if has_desc else \
self.MX + (zone_w - pyr_w) / 2
cx = pyr_x + pyr_w / 2
gap = 0.15
avail = self.CONT_H - 0.4
stage_h = (avail - gap * (n - 1)) / n
y = self._cy(avail) + 0.2
for i, nv in enumerate(niveaux):
if not isinstance(nv, dict):
nv = {"label": str(nv)}
wt = widths[i] * pyr_w
color = colors[i]
txt_color = self.C["navy"] if color == self.C["glacier"] \
else self.C["white"]
self._rect(slide, cx - wt / 2, y, wt, stage_h, color)
self._text(slide, cx - wt / 2 + 0.3, y, wt - 0.6, stage_h,
as_label(nv, "label", "titre"),
size=16, bold=True, color=txt_color,
align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)
desc = pick(nv, "description", "detail")
if desc:
dx = pyr_x + pyr_w + 1.2
self._text(slide, dx, y,
self.SLIDE_W - self.MX - dx, stage_h, desc,
size=13, color=self.C["body"],
anchor=MSO_ANCHOR.MIDDLE)
y += stage_h + gap
'''
PATCHES = [
# REGISTRY
(
" \"matrix_2x2\": \"_render_matrix_2x2\",\n",
" \"matrix_2x2\": \"_render_matrix_2x2\",\n"
" \"agenda\": \"_render_agenda\",\n"
" \"pyramid\": \"_render_pyramid\",\n",
),
]
MARKER = "_render_agenda"
ANCHOR_ORCH = " # ---------------- orchestration ----------------"
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)
content = TARGET.read_text(encoding="utf-8")
if MARKER in content:
fail("Déjà patché (_render_agenda présent) — rien à faire.")
for i, (old, _) in enumerate(PATCHES, 1):
n = content.count(old)
if n == 0:
fail("Ancre du patch %d introuvable — moteur inattendu." % i)
if n > 1:
fail("Ancre du patch %d non unique (%d occurrences)." % (i, n))
if content.count(ANCHOR_ORCH) != 1:
fail("Ancre de la section orchestration introuvable ou non "
"unique.")
shutil.copy2(TARGET, str(TARGET) + ".bak-c5c")
print(" + Sauvegarde : %s.bak-c5c" % TARGET)
for old, new in PATCHES:
content = content.replace(old, new)
lines = content.split("\n")
idx = next(i for i, l in enumerate(lines) if ANCHOR_ORCH in l)
lines[idx:idx] = COMPONENTS.split("\n")
content = "\n".join(lines)
TARGET.write_text(content, encoding="utf-8")
print(" + Renderers agenda + pyramid + REGISTRY appliqués.")
try:
py_compile.compile(str(TARGET), doraise=True)
print(" + Compilation OK.")
except py_compile.PyCompileError as e:
shutil.copy2(str(TARGET) + ".bak-c5c", TARGET)
fail("Erreur de compilation — fichier restauré :\n%s" % e)
print("\n Suite : python3 patch_layouts_c5c.py puis propagation "
"(prompt_injection_v2 + build_gallery).")
if __name__ == "__main__":
main()