"""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"