Files
oto-enterprise-os-dtp/05_deliverables_mvp/demo/scenarios/scenlib/render.py
T
Claude Code DTP Worker aebe9b7ba9 [DTP-Worker] Sprint 7/8 · Livrable démo : prompteur Markdown (out/run_sheet.md)
Le seul livrable démo (demo/scenarios) ne produisait qu'un run-sheet JSON
machine — aucun support lisible par un présentateur, alors que le Sprint 7 vise
un scénario « prêt à jouer ». Rendu Markdown in-repo depuis le JSON = même
pattern que faisabilite (rend des .md), zéro écriture VPS.

- scenlib/render.py : render_markdown() PUR/déterministe, ne lit que le
  run-sheet (déjà anti-inventé), aucun chiffre nouveau (#6).
- build émet out/run_sheet.md (prompteur : par beat, table « À dire | Chiffre |
  Source (preuve) » traçant le pointeur RFC 6901 amont).
- Compat vérifiée : HANDOFF 4Big ne json.load que les .json (ignore .md) ;
  check_artifacts diffe tout fichier build → md commité + reproductible.
- Consommateurs régénérés : régression 551→558 (22 suites PASS) ;
  quality_report.json (README 5011→5575 o · 32→39 test_*) PASS 22/22 ;
  03_agents/qa/AGENT.md 551→558 ; README démo auto-score 96→97.
- 7 gates verts · 39 tests démo · 34 tests audit · arbre propre.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 04:09:23 +00:00

94 lines
3.8 KiB
Python

"""Rendu Markdown du run-sheet démo — le **prompteur** lisible par un présentateur.
Transforme PUREMENT le `run_sheet.json` (déjà résolu, déjà anti-inventé) en un
script de pitch lisible à voix haute. Contraintes :
- **Zéro donnée nouvelle (CLAUDE.md #6)** : ce module ne lit QUE le run-sheet ;
chaque chiffre du Markdown provient d'une citation `evidence.valeur` déjà
résolue depuis un artefact amont. Aucune valeur n'est saisie, calculée ni
reformatée en un autre nombre.
- **Déterministe** : mêmes entrées ⇒ mêmes octets (aucun horodatage implicite ;
`generated_at` est celui, explicite, du run-sheet). C'est ce qui permet au
gate `ci/check_artifacts.sh` de prouver la reproductibilité de `out/run_sheet.md`.
Le rendu final riche (deck, page démo `otov7.com`) reste côté VPS (#8) ; ce
prompteur Markdown est le support **texte** in-repo que ce rendu habillera.
"""
from __future__ import annotations
from . import evidence
def _fmt(valeur) -> str:
"""Formate une valeur de citation pour l'oral, sans jamais inventer.
Un placeholder non résolu (ne devrait pas survivre aux invariants, mais on
reste défensif) est rendu verbatim entre back-ticks — jamais promu en chiffre.
"""
if isinstance(valeur, bool):
return "oui" if valeur else "non"
if isinstance(valeur, list):
return ", ".join(_fmt(v) for v in valeur)
if valeur is None:
return "`—`"
if evidence.is_placeholder(valeur):
return f"`{valeur}`"
return str(valeur)
def _scenario_md(sc: dict) -> list[str]:
lignes: list[str] = []
lignes.append(f"## {sc['titre']}")
lignes.append("")
lignes.append(
f"- **Scénario** : `{sc['id']}` · **audience** : {sc['audience']} · "
f"**projet** : {sc['projet']}{sc['projet_libelle']}"
)
lignes.append(f"- **Durée** : {sc['duree_min']} min · {len(sc['beats'])} temps forts")
lignes.append(f"- **Objectif** : {sc['objectif']}")
lignes.append("")
for b in sc["beats"]:
lignes.append(f"### {b['ordre']}. {b['titre']} · {b['duree_min']} min")
lignes.append("")
lignes.append(f"> {b['role_narratif']}")
lignes.append("")
lignes.append("| À dire | Chiffre | Source (preuve) |")
lignes.append("|---|---|---|")
for c in b["evidence"]:
src = f"`{c['module']}` · `{c['file']}#{c['pointer']}`"
lignes.append(f"| {c['label']} | {_fmt(c['valeur'])} | {src} |")
lignes.append("")
return lignes
def render_markdown(run_sheet: dict) -> str:
"""Run-sheet dict → prompteur Markdown (chaîne terminée par un saut de ligne)."""
lignes: list[str] = []
lignes.append(f"# {run_sheet['doc']}")
lignes.append("")
lignes.append(
"> Prompteur généré **automatiquement** depuis `out/run_sheet.json` "
"(`demo_scenario_gen build`). Chaque chiffre est une **preuve re-résolue** "
"depuis un hand-off `out/` d'un module gaté — jamais saisi à la main "
"(anti-invention · CLAUDE.md #6). **Ne pas éditer à la main** : régénérer."
)
lignes.append("")
lignes.append(f"- **Roadmap** : {run_sheet['roadmap_ref']}")
lignes.append(f"- **Version du spec** : {run_sheet['spec_version']}")
total = sum(sc["duree_min"] for sc in run_sheet["scenarios"])
lignes.append(
f"- **{len(run_sheet['scenarios'])} scénario(s)** · durée cumulée {total} min"
)
if run_sheet.get("generated_at"):
lignes.append(f"- **Généré le** : {run_sheet['generated_at']}")
lignes.append("")
lignes.append("---")
lignes.append("")
for i, sc in enumerate(run_sheet["scenarios"]):
lignes += _scenario_md(sc)
if i < len(run_sheet["scenarios"]) - 1:
lignes.append("---")
lignes.append("")
return "\n".join(lignes).rstrip("\n") + "\n"