diff --git a/03_agents/qa/AGENT.md b/03_agents/qa/AGENT.md index af76ce0..4aa345d 100644 --- a/03_agents/qa/AGENT.md +++ b/03_agents/qa/AGENT.md @@ -32,7 +32,7 @@ périmètre — un auditeur ne s'audite jamais. **conformité** normative des hand-off. ## Verdict agrégé courant (source `qa/regression/out/regression_run.json`) -**22 suites gated · 551 tests · 551 passés · 0 échec · 0 erreur → verdict `PASS`.** +**22 suites gated · 558 tests · 558 passés · 0 échec · 0 erreur → verdict `PASS`.** Ce compte est **découvert automatiquement depuis le CI** (`q4lib.registry.parse_ci`, réutilisé sans duplication) — jamais compté à la main. diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index 275e724..9a5c6a4 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,64 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_040004` · Sprint 7/8 · Livrable démo : ajout du **prompteur Markdown** (`out/run_sheet.md`) — le support texte lisible manquait au « scénario démo prêt à jouer » + +**Tâche** : **Sprint 7 l.68** (« CRM + Faisabilité : Scénarios démo P07 banquier · +P05 client ready ») × **Sprint 8 buffer**. Après audit : les 7 gates CI étaient +verts, 15 promesses `in_repo`, faisabilité 4 volets complète. Le seul livrable démo +(`demo/scenarios`) ne produisait qu'un **run-sheet JSON machine** — aucun support +**lisible par un présentateur**. Son propre README plafonnait l'auto-score à 96/100 +en qualifiant le « prompteur » de **hors périmètre (VPS)**. Or rendre un Markdown +in-repo depuis le JSON est exactement le pattern déjà utilisé par `faisabilite` +(rend des `.md`) — **aucune écriture VPS**. Gap réel, borné, à forte valeur. + +### Livraison + +- **`scenlib/render.py`** (nouveau) — `render_markdown(run_sheet) -> str` **pur et + déterministe**. Ne lit **que** le run-sheet (déjà résolu, déjà anti-inventé) : + aucun chiffre nouveau, aucun recalcul (anti-invention #6). `_fmt` : bool→oui/non, + liste→jointe, placeholder survivant rendu entre back-ticks (jamais promu en nombre). +- **`demo_scenario_gen.py build`** émet désormais `out/run_sheet.md` en plus des deux + JSON. Le rendu est un prompteur par scénario : en-tête (audience/projet/durée/ + objectif), puis chaque beat en table « À dire | Chiffre | Source (preuve) » — + chaque ligne trace son pointeur RFC 6901 amont. +- **`out/run_sheet.md`** (nouveau, commité) — 2 scénarios · 12 beats · 24 min cumulés. + +### Compatibilité architecture (vérifiée AVANT d'écrire) + +- **HANDOFF 4Big** (`q4lib/criteria.py:91`) ne `json.load` que les fichiers `.json` — + un `.md` dans `out/` est **ignoré**, aucune casse du critère. +- **`ci/check_artifacts.sh`** diffe **tout** fichier produit par `build` → il exige + désormais `run_sheet.md` **commité + reproductible byte-for-byte** (render + déterministe ⇒ OK). Preuve de morsure implicite : fichier non suivi ⇒ gate exit 1 + (constaté puis corrigé par `git add`). + +### Consommateurs régénérés (dérive maîtrisée) + +- +7 méthodes test (`TestRenderMarkdown`) → **régression 551→558 tests** (22 suites, + PASS) : `regression run` + `build` régénérés (`regression_run/plan/MANIFEST.json`). +- `demo/scenarios` : README `5011→5575` octets + `32→39` test_* → **`quality_report.json` + régénéré** (evidence DOC+TESTS) · verdict **PASS · 22/22 · min 100** inchangé. +- `03_agents/qa/AGENT.md` : verdict agrégé `551→558` (gate `check_readme_claims` vert). +- README démo : Fichiers (+`render.py`, +`run_sheet.md`), chaîne de valeur, « hors + périmètre » (seul l'**habillage visuel** reste VPS), auto-score **96→97**. + +### Vérifs + +| Contrôle | Résultat | +|---|---| +| 7 gates CI (`guard`/`json`/`docs`/`artifacts`/`regression`/`readme_claims`/`ci_integrity`) | ✅ exit 0 | +| Tests démo | ✅ 39/39 | +| Tests audit_4big | ✅ 34/34 | +| Régression exhaustive | ✅ 22/22 suites · 558 tests · PASS | +| `run_sheet.md` reproductible (build frais == commité) | ✅ (gate artifacts) | +| Arbre de travail | ✅ 0 fichier untracked | + +**Hors périmètre worker (VPS · #8)** : habillage visuel deck / page démo `otov7.com` + +branchement `data_room` réel — inchangé. Le prompteur texte est le rendu de référence +in-repo que ce visuel habillera. + +**Auto-score 4Big** : 97/100. + ## Session `20260731_033001` · Buffer S8 · « Vert trompeur » : la fiche QA affichait un « verdict agrégé courant » PÉRIMÉ (21/534) en se disant « jamais compté à la main » — hors du périmètre du gate README **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement diff --git a/05_deliverables_mvp/demo/scenarios/README.md b/05_deliverables_mvp/demo/scenarios/README.md index 428821f..e72f744 100644 --- a/05_deliverables_mvp/demo/scenarios/README.md +++ b/05_deliverables_mvp/demo/scenarios/README.md @@ -25,8 +25,8 @@ scenario_spec.json out/ des modules livrés (10 modules cités) │ │ ▼ demo_scenario_gen build ▼ out/run_sheet.json ◄──── résolution RFC 6901 + placeholders ──── crm/workflow_vente - out/MANIFEST.json fiscal/ecf_dgii - legal/confotur + out/run_sheet.md (prompteur, rendu pur du run-sheet) fiscal/ecf_dgii + out/MANIFEST.json legal/confotur rbac/apply_plan qa/audit_4big … ``` @@ -52,13 +52,13 @@ jamais un `0` fabriqué, jamais un `null` amont promu en chiffre de pitch. ## Utilisation ```bash -# Assemble le run-sheet (défaut: ./out) +# Assemble le run-sheet + le prompteur Markdown (défaut: ./out) python3 demo_scenario_gen.py build [-o OUT_DIR] # Recalcule le manifeste + les invariants, sans écrire python3 demo_scenario_gen.py validate -# Tests (32 méthodes, dont injections négatives) +# Tests (39 méthodes, dont injections négatives) python3 -m unittest discover -s tests -v ``` @@ -70,16 +70,21 @@ python3 -m unittest discover -s tests -v | `scenlib/deps.py` | Réutilise validateur Publiciste + couverture CI 4Big | | `scenlib/evidence.py` | Chargement `out/` amont + résolveur RFC 6901 + placeholders | | `scenlib/builder.py` | Assemblage déterministe run-sheet + manifeste | +| `scenlib/render.py` | Rendu **prompteur Markdown** (pur, dérivé du run-sheet) | | `demo_scenario_gen.py` | CLI `build`/`validate` + invariants | | `scenario.schema.json` | Contrat de sortie draft-07 du manifeste | -| `out/run_sheet.json` | **Hand-off principal** — run-sheet jouable | +| `out/run_sheet.json` | **Hand-off principal** — run-sheet jouable (machine) | +| `out/run_sheet.md` | **Prompteur** lisible par un présentateur (dérivé de `run_sheet.json`) | | `out/MANIFEST.json` | Diagnostic + preuve de couverture + drapeau `pret` | ## Hors périmètre worker (VPS · #8) -Le rendu final (deck / prompteur / page démo `otov7.com`) et le branchement au -`data_room` réel des projets restent **côté serveur** — ce worker n'écrit jamais -sur le VPS. Le run-sheet est le **contrat** que ce rendu consommera. +Le **support texte** est désormais produit in-repo : `out/run_sheet.md` est un +prompteur lisible, régénéré déterministement depuis le run-sheet (aucune donnée +nouvelle · #6). Ne restent **côté serveur** que l'**habillage visuel** (deck / +page démo `otov7.com`) et le branchement au `data_room` réel des projets — ce +worker n'écrit jamais sur le VPS. Le run-sheet JSON reste le **contrat** que ce +rendu visuel consommera ; le Markdown en est le rendu texte de référence. ## Provenance des libellés projets @@ -88,9 +93,10 @@ sur le VPS. Le run-sheet est le **contrat** que ce rendu consommera. ## Auto-score 4Big -**96/100** — orchestration réelle prouvée (chaque beat cite une preuve RFC 6901 +**97/100** — orchestration réelle prouvée (chaque beat cite une preuve RFC 6901 re-résolue depuis le disque, jamais saisie à la main), couverture CI réutilisée de l'auditeur 4Big (zéro duplication · #5), garde `pret=true` qui refuse d'écrire sur -citation non résolue, 32 tests (dont injections négatives) verts. Retenue sur le -seuil : le rendu final (deck / page démo `otov7.com`) est produit côté VPS (#8) — -l'in-repo est le **run-sheet** contractuel, pas le support de présentation. +citation non résolue, **prompteur `run_sheet.md`** désormais livré (support texte +in-repo, dérivé purement du run-sheet · reproductible sous `ci/check_artifacts.sh`), +39 tests (dont injections négatives + rendu Markdown) verts. Retenue résiduelle : +l'**habillage visuel** (deck / page démo `otov7.com`) reste produit côté VPS (#8). diff --git a/05_deliverables_mvp/demo/scenarios/demo_scenario_gen.py b/05_deliverables_mvp/demo/scenarios/demo_scenario_gen.py index 76a0c0f..abbd80f 100644 --- a/05_deliverables_mvp/demo/scenarios/demo_scenario_gen.py +++ b/05_deliverables_mvp/demo/scenarios/demo_scenario_gen.py @@ -42,7 +42,7 @@ import sys _HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, _HERE) -from scenlib import builder, deps, evidence # noqa: E402 +from scenlib import builder, deps, evidence, render # noqa: E402 def _eprint(*args) -> None: @@ -197,9 +197,13 @@ def cmd_build(ns: argparse.Namespace) -> int: with open(os.path.join(ns.out, name), "w", encoding="utf-8") as fh: json.dump(obj, fh, ensure_ascii=False, indent=2) fh.write("\n") + # Prompteur Markdown lisible (dérivé PUREMENT du run-sheet · #6). Reproductible + # à l'identique (gate ci/check_artifacts.sh) car render est déterministe. + with open(os.path.join(ns.out, "run_sheet.md"), "w", encoding="utf-8") as fh: + fh.write(render.render_markdown(run_sheet)) c = manifest["counts"] - _eprint(f"✅ run-sheet → {ns.out}") + _eprint(f"✅ run-sheet → {ns.out} (+ prompteur run_sheet.md)") _eprint(f" scénarios={c['scenarios']} · beats={c['beats_total']} · " f"citations={c['citations_total']} (non résolues={c['citations_non_resolues']}) · " f"modules cités={c['modules_cites_uniques']}") diff --git a/05_deliverables_mvp/demo/scenarios/out/run_sheet.md b/05_deliverables_mvp/demo/scenarios/out/run_sheet.md new file mode 100644 index 0000000..8f02e97 --- /dev/null +++ b/05_deliverables_mvp/demo/scenarios/out/run_sheet.md @@ -0,0 +1,130 @@ +# OTO · Run-sheet démo (méta-livrable) + +> 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. + +- **Roadmap** : 04_roadmap/ROADMAP_8_WEEKS_OR_LESS.md · Sprint 7 l.68 « CRM + Faisabilité : Scénarios démo (P07 pitch banquier · P05 client ready) » +- **Version du spec** : 1.0.0 +- **2 scénario(s)** · durée cumulée 24 min + +--- + +## P07 Aqua Terra — Pitch banquier (bancabilité & gouvernance) + +- **Scénario** : `S-P07-BANQUIER` · **audience** : banquier · **projet** : P07 — Aqua Terra Las Terrenas +- **Durée** : 12 min · 6 temps forts +- **Objectif** : Démontrer qu'un dossier de vente OTO est bancable : pipeline structuré, conformité fiscale e-CF DGII, incitation CONFOTUR, gouvernance RBAC et gate qualité 4Big. + +### 1. Un pipeline de vente verrouillé de bout en bout · 2 min + +> Ouvrir sur la traçabilité : la vente n'est pas un tableur, c'est un workflow ERPNext soumis (docstatus) piloté par rôle. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Nom du pipeline | OTO Vente Pipeline | `crm/workflow_vente` · `out/workflow.json#/0/workflow_name` | +| DocType piloté | OTO Dossier Vente | `crm/workflow_vente` · `out/workflow.json#/0/document_type` | + +### 2. Facturation fiscale conforme DGII (e-CF · Compupar) · 2 min + +> Rassurer sur le risque fiscal : chaque encaissement est un e-CF certifié, pas une facture maison. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Fournisseur e-CF | Compupar | `fiscal/ecf_dgii` · `out/MANIFEST.json#/provider` | +| Types e-CF en périmètre | 5 | `fiscal/ecf_dgii` · `out/MANIFEST.json#/counts/tipos_en_scope` | + +### 3. Incitation CONFOTUR dé-risquée et outillée · 2 min + +> Montrer l'avantage fiscal touristique comme un actif documenté : DocType soumis, dépôt outillé. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| DocType CONFOTUR | CONFOTUR Application | `legal/confotur` · `out/MANIFEST.json#/doctype_name` | +| Soumissible (verrou légal) | oui | `legal/confotur` · `out/MANIFEST.json#/is_submittable` | +| Étapes de dépôt outillées | 2 | `legal/confotur` · `out/MANIFEST.json#/counts/depot_events` | + +### 4. Séparation des pouvoirs (RBAC 50 rôles) · 2 min + +> Répondre à la question d'audit du banquier : qui peut faire quoi ? Permissions dérivées, pas ad hoc. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Rôles RBAC | 50 | `rbac/apply_plan` · `out/MANIFEST.json#/counts/roles` | +| Permissions DocType (Custom DocPerm) | 116 | `rbac/apply_plan` · `out/MANIFEST.json#/counts/custom_docperm` | + +### 5. Un gate qualité 4Big, pas une promesse · 2 min + +> Clore la confiance : la plateforme s'auto-note et bloque sous 95/100 sur 100 % des livrables. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Seuil bloquant 4Big | 95 | `qa/audit_4big` · `out/quality_report.json#/pass_score` | +| Modules sous gate qualité | 22 | `qa/audit_4big` · `out/quality_report.json#/coverage/ci_modules_count` | + +### 6. Le dossier de vente, pièce bancable finale · 2 min + +> Refermer sur l'objet concret que le banquier instruira : un DocType riche et soumis. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Champs du dossier de vente | 30 | `crm/dossier_vente` · `out/MANIFEST.json#/counts/fields` | +| États de pipeline couverts | 9 | `crm/dossier_vente` · `out/MANIFEST.json#/counts/pipeline_states` | + +--- + +## P05 Las Colinas Najayo Arriba — Parcours client (réservation prête) + +- **Scénario** : `S-P05-CLIENT` · **audience** : client · **projet** : P05 — Las Colinas Najayo Arriba +- **Durée** : 12 min · 6 temps forts +- **Objectif** : Faire vivre le parcours d'un acheteur : découverte du portail, assistance OTOIA, pipeline lisible, dossier transparent, et comment le client nous a trouvés (SEO trilingue). + +### 1. « Choisir mon unité » — 5 portails luxury · 2 min + +> Poser l'expérience d'entrée : un portail par rôle, cohérent et couvert. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Portails (workspaces) | 5 | `frontend/portails` · `out/MANIFEST.json#/counts/workspaces` | +| Rôles couverts par les portails | 44 | `frontend/portails` · `out/MANIFEST.json#/counts/roles_couverts` | + +### 2. OTOIA répond dans le portail · 2 min + +> Montrer l'assistance embarquée : le client n'est jamais seul face à l'écran. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Assistant embarqué | Chat OTOIA embarqué par portail | `frontend/chat_otoia` · `out/MANIFEST.json#/deliverable` | + +### 3. Du premier contact à la réservation · 2 min + +> Dérouler le pipeline côté client : Lead → … → Réservation confirmée, lisible et rassurant. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Première étape | Lead | `crm/workflow_vente` · `out/workflow.json#/0/states/0/state` | +| Étape réservation | Réservation confirmée | `crm/workflow_vente` · `out/workflow.json#/0/states/3/state` | + +### 4. Un dossier de vente transparent · 2 min + +> Donner confiance : tout est consigné dans un dossier structuré que le client peut suivre. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Champs du dossier de vente | 30 | `crm/dossier_vente` · `out/MANIFEST.json#/counts/fields` | + +### 5. Un conseiller rémunéré à la transparence · 2 min + +> Aligner l'intérêt du conseiller et du client : barème de commissions traçable par événement. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Barème de commissions | OTO Barème Commissions Ventes | `crm/commissions` · `out/MANIFEST.json#/bareme_name` | +| Événements déclencheurs | 5 | `crm/commissions` · `out/MANIFEST.json#/counts/evenements` | + +### 6. Comment le client nous a trouvés (SEO trilingue) · 2 min + +> Refermer la boucle acquisition : indexation FR/EN/ES, le parcours commence bien avant le portail. + +| À dire | Chiffre | Source (preuve) | +|---|---|---| +| Mots-clés indexés | 258 | `seo` · `out/MANIFEST.json#/counts/keywords_total` | +| Langues | fr, en, es | `seo` · `out/MANIFEST.json#/langs` | diff --git a/05_deliverables_mvp/demo/scenarios/scenlib/render.py b/05_deliverables_mvp/demo/scenarios/scenlib/render.py new file mode 100644 index 0000000..3776df5 --- /dev/null +++ b/05_deliverables_mvp/demo/scenarios/scenlib/render.py @@ -0,0 +1,93 @@ +"""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" diff --git a/05_deliverables_mvp/demo/scenarios/tests/test_demo_scenario.py b/05_deliverables_mvp/demo/scenarios/tests/test_demo_scenario.py index 9b65279..b1e00c6 100644 --- a/05_deliverables_mvp/demo/scenarios/tests/test_demo_scenario.py +++ b/05_deliverables_mvp/demo/scenarios/tests/test_demo_scenario.py @@ -19,7 +19,7 @@ _MOD = os.path.normpath(os.path.join(_HERE, "..")) sys.path.insert(0, _MOD) import demo_scenario_gen as gen # noqa: E402 -from scenlib import builder, evidence # noqa: E402 +from scenlib import builder, evidence, render # noqa: E402 def _fresh(): @@ -214,6 +214,51 @@ class TestInjectionsNegatives(unittest.TestCase): self.assertTrue(gen.deps.validate(mf, gen._load_schema())) +class TestRenderMarkdown(unittest.TestCase): + """Le prompteur Markdown : déterministe et sans donnée nouvelle (#6).""" + + def setUp(self): + _, self.run_sheet, _ = _fresh() + self.md = render.render_markdown(self.run_sheet) + + def test_deterministe(self): + self.assertEqual(self.md, render.render_markdown(self.run_sheet)) + + def test_termine_par_un_saut_de_ligne(self): + self.assertTrue(self.md.endswith("\n")) + self.assertFalse(self.md.endswith("\n\n")) + + def test_titres_scenarios_presents(self): + for sc in self.run_sheet["scenarios"]: + self.assertIn(sc["titre"], self.md) + self.assertIn(sc["id"], self.md) + + def test_chaque_beat_rendu(self): + for sc in self.run_sheet["scenarios"]: + for b in sc["beats"]: + self.assertIn(b["titre"], self.md) + self.assertIn(b["role_narratif"], self.md) + + def test_valeurs_proviennent_du_run_sheet(self): + # Anti-invention : chaque valeur affichée est celle, formatée, d'une + # citation — jamais un chiffre nouveau. On vérifie la présence verbatim. + for sc in self.run_sheet["scenarios"]: + for b in sc["beats"]: + for c in b["evidence"]: + self.assertIn(render._fmt(c["valeur"]), self.md) + + def test_fmt_bool_liste_none(self): + self.assertEqual(render._fmt(True), "oui") + self.assertEqual(render._fmt(False), "non") + self.assertEqual(render._fmt(["fr", "en"]), "fr, en") + self.assertEqual(render._fmt(50), "50") + self.assertEqual(render._fmt(None), "`—`") + + def test_placeholder_non_promu_en_chiffre(self): + # Un placeholder survivant est rendu entre back-ticks, jamais nu. + self.assertEqual(render._fmt("{{m:f#/p}}"), "`{{m:f#/p}}`") + + class TestCLI(unittest.TestCase): def test_validate_retourne_zero(self): self.assertEqual(gen.main(["validate"]), 0) @@ -222,7 +267,7 @@ class TestCLI(unittest.TestCase): import tempfile with tempfile.TemporaryDirectory() as d: self.assertEqual(gen.main(["build", "-o", d]), 0) - for name in ("run_sheet.json", "MANIFEST.json"): + for name in ("run_sheet.json", "MANIFEST.json", "run_sheet.md"): self.assertTrue(os.path.isfile(os.path.join(d, name))) diff --git a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json index b817186..311fd04 100644 --- a/05_deliverables_mvp/qa/audit_4big/out/quality_report.json +++ b/05_deliverables_mvp/qa/audit_4big/out/quality_report.json @@ -774,7 +774,7 @@ "criterion": "DOC", "weight": 20, "passed": true, - "evidence": "README.md (5011 octets)" + "evidence": "README.md (5575 octets)" }, { "criterion": "CONTRAT", @@ -786,7 +786,7 @@ "criterion": "TESTS", "weight": 25, "passed": true, - "evidence": "32 méthodes test_* dans 1 fichier(s) (seuil 8)" + "evidence": "39 méthodes test_* dans 1 fichier(s) (seuil 8)" }, { "criterion": "CLI", diff --git a/05_deliverables_mvp/qa/regression/out/MANIFEST.json b/05_deliverables_mvp/qa/regression/out/MANIFEST.json index 3230a7c..d575efa 100644 --- a/05_deliverables_mvp/qa/regression/out/MANIFEST.json +++ b/05_deliverables_mvp/qa/regression/out/MANIFEST.json @@ -5,7 +5,7 @@ "version": "1.0", "verdict": "PASS", "suites": 22, - "test_methods": 551, + "test_methods": 558, "coverage_ok": true, "artifacts": [ "regression_plan.json" diff --git a/05_deliverables_mvp/qa/regression/out/regression_plan.json b/05_deliverables_mvp/qa/regression/out/regression_plan.json index d1f09da..a131180 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_plan.json +++ b/05_deliverables_mvp/qa/regression/out/regression_plan.json @@ -68,7 +68,7 @@ "in_gate": true, "has_tests_dir": true, "test_files": 1, - "test_methods": 32 + "test_methods": 39 }, { "id": "devops.deploy_runbook", @@ -273,7 +273,7 @@ "totals": { "suites": 22, "test_files": 22, - "test_methods": 551, + "test_methods": 558, "min_methods": 10, "under_threshold": 0 }, diff --git a/05_deliverables_mvp/qa/regression/out/regression_run.json b/05_deliverables_mvp/qa/regression/out/regression_run.json index 270477d..d4fd725 100644 --- a/05_deliverables_mvp/qa/regression/out/regression_run.json +++ b/05_deliverables_mvp/qa/regression/out/regression_run.json @@ -42,9 +42,9 @@ { "id": "demo.scenarios", "path": "demo/scenarios", - "expected_methods": 32, - "ran": 32, - "passed": 32, + "expected_methods": 39, + "ran": 39, + "passed": 39, "failures": 0, "errors": 0, "skipped": 0, @@ -272,8 +272,8 @@ "suites": 22, "green": 22, "red": 0, - "ran": 551, - "passed": 551, + "ran": 558, + "passed": 558, "failures": 0, "errors": 0, "skipped": 0