aebe9b7ba9
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>
94 lines
3.8 KiB
Python
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"
|