From 9794eabc8afc500a7fd9af2103d7e47e605d3648 Mon Sep 17 00:00:00 2001 From: Claude Code DTP Worker Date: Fri, 31 Jul 2026 16:39:55 +0000 Subject: [PATCH] =?UTF-8?q?[DTP-Worker]=20Sprint=208=20=C2=B7=20buffer=20L?= =?UTF-8?q?75=20=C2=B7=20Domaine=20CRM/Commissions=20:=20la=20CROSS-COH?= =?UTF-8?q?=C3=89RENCE=20du=20bar=C3=A8me=20vendeurs=20(=C2=AB=20c=C5=93ur?= =?UTF-8?q?=20du=20livrable=20=C2=BB)=20=E2=80=94=20=C3=A9tats=20BROUILLON?= =?UTF-8?q?=20exclus=20(`lead/visite/devis/abandonn=C3=A9`),=20CHAMPS=20DE?= =?UTF-8?q?=20BASE=20(`montant=5Freservation`,=20`montant=5Fcontrat`)=20et?= =?UTF-8?q?=20INVARIANT=203=20=C2=AB=20on=20ne=20commissionne=20QUE=20des?= =?UTF-8?q?=20=C3=A9tats=20soumis=20(doc=5Fstatus=3D1)=20=C2=BB=20?= =?UTF-8?q?=E2=80=94=20=C3=A9tait=20=C3=A9num=C3=A9r=C3=A9e=20=C3=80=20LA?= =?UTF-8?q?=20MAIN=20dans=20le=20README=20du=20module=20`crm/commissions`?= =?UTF-8?q?=20sans=20AUCUN=20gate=20d'IDENTIT=C3=89.=20Le=20bloc=20racine?= =?UTF-8?q?=20ne=20gate=20que=20le=20COMPTE=20de=20tests=20(=C2=AB=2025=20?= =?UTF-8?q?=C2=BB,=20via=20l'agr=C3=A9gat=20=C2=AB=20Total=20CRM=20=C2=BB)?= =?UTF-8?q?=20=E2=80=94=20surface=20distincte.=20Ces=20trois=20faits=20son?= =?UTF-8?q?t=20DATA-DERIVED=20:=20brouillons=20=3D=20{update=5Fvalue=20|?= =?UTF-8?q?=20doc=5Fstatus=3D=3D0}=20de=20`crm/workflow=5Fvente/out/workfl?= =?UTF-8?q?ow.json`=20(=3D=20{lead,visite,devis,abandonne})=20=C2=B7=20cha?= =?UTF-8?q?mps=20de=20base=20=3D=20{base=5Ffield}=20des=20=C3=A9v=C3=A8nem?= =?UTF-8?q?ents=20de=20`crm/commissions/out/commission=5Fplan.json`=20?= =?UTF-8?q?=C2=B7=20=C3=A9tats=20soumis=20=3D=20{update=5Fvalue=20|=20doc?= =?UTF-8?q?=5Fstatus=3D=3D1}=20=E2=80=94=20les=20deux=20artefacts=20byte-g?= =?UTF-8?q?at=C3=A9s=20par=20check=5Fartifacts.=20Aucun=20gate=20ne=20comp?= =?UTF-8?q?arait=20ces=20ENSEMBLES=20=C3=A0=20la=20prose=20:=20PROMOUVOIR?= =?UTF-8?q?=20un=20brouillon=20en=20soumis=20(abandonne=20=E2=86=92=20doc?= =?UTF-8?q?=5Fstatus=201)=20le=20retire=20de=20l'ensemble=20brouillon=20pe?= =?UTF-8?q?ndant=20que=20la=20prose=20l'=C3=A9num=C3=A8re=20encore=20?= =?UTF-8?q?=C2=B7=20AJOUTER/RENOMMER=20un=20champ=20de=20base=20OU=20?= =?UTF-8?q?=C3=89CHANGER=20un=20=C3=A9tat=20passe=20aussi=20=E2=80=94=20?= =?UTF-8?q?=C2=AB=20vert=20trompeur=20=C2=BB=20qu'aucune=20suite=20tests/?= =?UTF-8?q?=20(qui=20teste=20des=20FONCTIONS=20finance/r=C3=A9solution,=20?= =?UTF-8?q?pas=20la=20prose)=20n'attrape=20=E2=86=92=20nouveau=20bloc=20re?= =?UTF-8?q?computant=20chaque=20ENSEMBLE=20depuis=20SON=20artefact=20byte-?= =?UTF-8?q?gat=C3=A9=20(z=C3=A9ro=20duplication=20du=20contrat=20du=20g?= =?UTF-8?q?=C3=A9n=C3=A9rateur=20#6)=20et=20exigeant=20que=20la=20prose=20?= =?UTF-8?q?l'=C3=A9num=C3=A8re=20EXACTEMENT.=20Contr=C3=B4le=20par=20ENSEM?= =?UTF-8?q?BLE=20(absent=20ET=20en=20trop=20mordus=20via=20set-diff=20?= =?UTF-8?q?=C2=B7=20accents/casse=20normalis=C3=A9s=20via=20unicodedata).?= =?UTF-8?q?=20Coh=C3=A9rence=20crois=C3=A9e=20en=20bonus=20(mord=20un=20pl?= =?UTF-8?q?an=20INTERNEMENT=20incoh=C3=A9rent=20avec=20le=20workflow)=20:?= =?UTF-8?q?=20tout=20update=5Fvalue=20commissionn=C3=A9=20=E2=88=88=20?= =?UTF-8?q?=C3=A9tats=20soumis=20ET=20=E2=88=89=20brouillons=20=E2=80=94?= =?UTF-8?q?=20l'INVARIANT=203=20du=20module,=20non=20vide.=20Un=20claim=20?= =?UTF-8?q?absent=20=C3=A9choue=20AUSSI.=205=20morsures=20v=C3=A9rifi?= =?UTF-8?q?=C3=A9es=20:=20=C3=A9change=20brouillon=20(abandonn=C3=A9?= =?UTF-8?q?=E2=86=92perdu)=20capt=C3=A9=20(absents=3D[abandonne]=20en=20tr?= =?UTF-8?q?op=3D[perdu])=20=C2=B7=20sous-ensemble=20champs=20de=20base=20(?= =?UTF-8?q?retrait=20montant=5Fcontrat)=20capt=C3=A9=20=C2=B7=20=C3=A9num?= =?UTF-8?q?=C3=A9ration=20des=20brouillons=20supprim=C3=A9e=20(INTROUVABLE?= =?UTF-8?q?)=20=C2=B7=20plan=20pointant=20un=20brouillon=20(reservation?= =?UTF-8?q?=E2=86=92lead,=20invariant=203=20cass=C3=A9)=20capt=C3=A9=20?= =?UTF-8?q?=C2=B7=20promotion=20de=20l'=C3=A9tat=20abandonne=20en=20soumis?= =?UTF-8?q?=20dans=20l'artefact=20(draft=20set=20change,=20=C2=AB=20en=20t?= =?UTF-8?q?rop=20=C2=BB=20dans=20la=20prose)=20capt=C3=A9=20;=20restaur?= =?UTF-8?q?=C3=A9=20=3D=20green=20:=20brouillons=20[abandonne,devis,lead,v?= =?UTF-8?q?isite]=20=3D=3D=20workflow=20(doc=5Fstatus=3D0)=20=C2=B7=20cham?= =?UTF-8?q?ps=20de=20base=20=3D=3D=20plan=20=C2=B7=20invariant=203=20vert.?= =?UTF-8?q?=20=C3=89tat=20courant=20:=20aucun=20ensemble=20p=C3=A9rim?= =?UTF-8?q?=C3=A9=20(anti-invention=20#6,=20rien=20=C3=A0=20r=C3=A9=C3=A9c?= =?UTF-8?q?rire)=20=E2=80=94=20le=20d=C3=A9faut=20est=20la=20surface=20ung?= =?UTF-8?q?ated.=20ci/README.md=20(table=20+=20d=C3=A9tail)=20mis=20=C3=A0?= =?UTF-8?q?=20jour=20=C2=B7=207=20gates=20re-verts.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- 05_activity_log/2026-07-31.md | 66 ++++++++++++++++++++++++ ci/README.md | 27 +++++++++- ci/check_readme_claims.sh | 94 ++++++++++++++++++++++++++++++++++- 3 files changed, 185 insertions(+), 2 deletions(-) diff --git a/05_activity_log/2026-07-31.md b/05_activity_log/2026-07-31.md index c568879..bb7d0a3 100644 --- a/05_activity_log/2026-07-31.md +++ b/05_activity_log/2026-07-31.md @@ -1,5 +1,71 @@ # Activity Log · 2026-07-31 · Claude Code DTP +## Session `20260731_163112` · Buffer S8 · Domaine CRM/Commissions : la **cross-cohérence** du barème vendeurs (« cœur du livrable ») — les états **brouillon exclus** (`lead/visite/devis/abandonné`), les **champs de base** (`montant_reservation`, `montant_contrat`) et l'**invariant 3** « on ne commissionne que des états soumis » — était énumérée À LA MAIN dans le README du module sans AUCUN gate d'IDENTITÉ ; le bloc racine ne gate que le COMPTE de tests (« 25 », via l'agrégat « Total CRM »). + +**Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement +close ; poursuite de la série anti-dérive (CLAUDE.md #6). Les sessions précédentes ont +gaté les domaines tests, portails, RBAC, SEO, CRM (pipeline vente + séparation des +pouvoirs), mobile (quadruplet + onglets), Legal/CONFOTUR, DevOps, QA (audit 5D + 4Big), +Démo, Frontend/Chat OTOIA, recette et Fiscal/e-CF. Cette session cible le **module +`crm/commissions`** — jamais gaté hors son compte de tests — dont la section +« Cross-cohérence » (le CŒUR du livrable) énumère trois faits data-derived pointant les +artefacts générés sans être recomputés par aucun gate. + +**Dérive silencieuse fermée** : +- `05_deliverables_mvp/crm/commissions/README.md:47` : « on ne commissionne pas un + **brouillon** (`lead/visite/devis/abandonné`) » — l'ensemble des états brouillon. +- `…/README.md:50` : « `base_field` … (`montant_reservation`, `montant_contrat`) » — + l'ensemble des champs de base. +- `…/README.md:46` : « correspondre à un état **soumis** (`doc_status = 1`) » — INVARIANT + 3 du module. +- Sources faisant autorité (byte-gatées par `check_artifacts`) : + `crm/workflow_vente/out/workflow.json` (brouillons = `{update_value | doc_status==0}` + = `{lead, visite, devis, abandonne}` · soumis = `{… | doc_status==1}`) et + `crm/commissions/out/commission_plan.json` (champs de base = `{base_field}` des + évènements · états payés = `{update_value}`). +- Le bloc racine ne gate QUE le **compte de tests** (« 25 »). Aucun gate ne comparait + ces ENSEMBLES aux artefacts : **PROMOUVOIR** un brouillon en soumis (`abandonne` → + doc_status 1) le retire de l'ensemble brouillon pendant que la prose l'énumère encore ; + **AJOUTER/RENOMMER** un champ de base ou **ÉCHANGER** un état passe aussi — « vert + trompeur » qu'aucune suite `tests/` (qui teste des FONCTIONS finance/résolution, pas la + prose) n'attrape. +- **État courant** : **aucun ensemble périmé** — les 2 énumérations recoupent les + artefacts exactement (anti-invention #6, rien à réécrire). Le défaut est la **surface + ungated**. + +**Nouveau bloc dans `ci/check_readme_claims.sh`** : recompute chaque ensemble depuis SON +artefact byte-gaté (zéro duplication du contrat du générateur · #6) et exige que la prose +l'énumère **exactement** — contrôle par ENSEMBLE (absent ET en trop mordus via set-diff · +accents/casse normalisés via `unicodedata`). Cohérence croisée en bonus (mord un plan +**internement incohérent** avec le workflow) : tout `update_value` commissionné ∈ états +soumis (doc_status=1) ET ∉ brouillons — l'**invariant 3**, non vide. Un claim absent +échoue AUSSI. + +### Preuve (5 morsures vérifiées + restauration verte) + +| Injection | Résultat | +|---|---| +| README brouillon `abandonné → perdu` (échange) | ✗ absents=`[abandonne]` · en trop=`[perdu]` · exit 1 | +| README base_field retire `montant_contrat` (sous-ensemble) | ✗ absents=`[montant_contrat]` · exit 1 | +| README énumération des brouillons supprimée | ✗ énumération des brouillons INTROUVABLE · exit 1 | +| artefact plan `reservation → lead` (commissionne un brouillon) | ✗ incohérence plan↔workflow : `[lead]` PAS soumis · invariant 3 cassé · exit 1 | +| artefact workflow `abandonne` doc_status `0→1` (draft set change) | ✗ prose en trop=`[abandonne]` vs brouillons workflow · exit 1 | +| restauré | ✓ brouillons `[abandonne,devis,lead,visite]` == workflow (doc_status=0) · champs de base == plan · invariant 3 vert · exit 0 | + +- **`ci/README.md`** — surface ajoutée à la table récap du pipeline + paragraphe + descriptif (classe « cross-cohérence du barème de commissions »). + +Suite CI complète re-verte (7 gates) : `guard_constraints` · `validate_json` · +`check_docs` · `check_artifacts` · `check_regression` · `check_ci_integrity` · +`check_readme_claims`. + +**Hors périmètre worker (VPS · #8)** : néant (gate bash/python3 stdlib en-repo ; +édition **hors** `05_deliverables_mvp/*/out` ⇒ 0 dérive d'artefact). + +**Auto-score 4Big** : 96/100. + +--- + ## Session `20260731_160111` · Buffer S8 · Domaine Fiscal/e-CF : le **contrat de format de l'e-NCF** (« `E + tipoeCF(2) + secuencia(10)` » = **13**) était retranscrit À LA MAIN dans **QUATRE** endroits de prose (README ×3 + fiche agent `erpnext_backend`) sans AUCUN gate d'IDENTITÉ — le bloc racine ne gate que le COMPTE de tests e-CF (« 39 »). **Tâche** : **Sprint 8 · buffer L75** (DevOps CI/CD · QA). Roadmap fonctionnellement diff --git a/ci/README.md b/ci/README.md index a9668ed..f6014a9 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,7 +20,7 @@ et manuellement (`workflow_dispatch`). Jobs statiques (+ une suite `unittest` pa | `check-artifacts` | `ci/check_artifacts.sh` | Reproductibilité : chaque `out/*.json` versionné == build frais | ✅ oui | | `check-regression` | `ci/check_regression.sh` | Fraîcheur : `qa/regression/out/regression_run.json` (`run`) == run frais + verdict PASS | ✅ oui | | `check-ci-integrity` | `ci/check_ci_integrity.sh` | Intégrité du câblage : `gate.needs` == tous les jobs non-manuels · chaque `ci/*.sh` câblé au gate | ✅ oui | -| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) == artefact cité (#6) | ✅ oui | +| `check-readme-claims` | `ci/check_readme_claims.sh` | Intégrité des chiffres des docs d'entrée : chaque nombre de `README.md` « État courant », de la fiche QA « Verdict agrégé » **et** de la colonne « Tests » par suite des tables de fiches **et** des agrégats en prose (Total CRM / RBAC / e-CF) **et** des comptes par workspace des portails (cartes/liens/rôles, ×2 docs) **et** des comptes RBAC des 3 volets (table Nb rôles/profil, ligne agrégée apply_plan, ventilation userperm) **et** des comptes de mots-clés SEO (total + fr/en/es, ×2 docs) **et** du pipeline vente CRM (états/transitions, ×2 docs + énumération des transitions à séparation des pouvoirs `{action | allow_self_approval=0}` recomputée de `workflow.json`) **et** de l'app mobile Expo (onglets/rôles/langues/identifiants a_confirmer + énumération ordonnée des onglets recomputée de `role_navigation`, bijection onglet⇔portail) **et** du DocType CONFOTUR (champs/données/sections/rôles/évènements de dépôt, ×2 docs) **et** de l'audit 5D (contrôles/dimensions ×2 docs + ventilation verdict pass/fail/à-confirmer + énumération des open items `control→dimension→propriétaire` recomputée de `audit_report.open_items`) **et** du run-book de déploiement VPS (phases ×2 dans la fiche devops + confirmations préalables count & énumération dans le README) **et** du run-sheet de démo (nombre de modules cités dans le diagramme du README) **et** du montage Chat OTOIA par portail (`Custom Block`/configs runtime/portails, tous = 5 aujourd'hui, ×2 docs) **et** du module de recette `qa/acceptance` (couple livrables de sprint + métriques succès MVP, ×2 docs de module au-delà du README d'entrée) **et** du barème de l'auditeur 4Big (nombre de critères en prose + poids par critère de la table + « generator (N critères) », recomputés de `quality_report.criteria`) **et** de la carte de renormalisation par archétype de l'auditeur 4Big (critères EXCLUS de `data_room`/`parser`/`contract`, recomputés des `checks` par archétype de `quality_report.modules`) **et** du contrat de format de l'e-NCF fiscal (`E + tipoeCF(2) + secuencia(10) = 13` : préfixe/longueurs des segments/longueur totale, ×3 README + fiche `erpnext_backend`, recomputés du bloc `e_ncf` d'`ecf_plan.json` + cohérences croisées `1+tipo+seq==longueur` & `|regex digits|==tipo+seq`) **et** de la cross-cohérence du barème de commissions `crm/commissions` (états brouillon exclus recomputés de `{doc_status=0}` de `workflow.json` + champs de base recomputés de `{base_field}` de `commission_plan.json` + invariant 3 « commissionne seulement des états soumis ») == artefact cité (#6) | ✅ oui | | `gate` | — | Agrégat vert = gate qualité 4Big franchi | ✅ oui | Aucune dépendance réseau/marketplace hors `actions/checkout`. Tout tourne avec @@ -397,6 +397,31 @@ fiche `erpnext_backend` `secuencia 10→9` · composition README supprimée (INTROUVABLE) ; plus la morsure d'incohérence d'artefact (`regex` désaligné du segment) ; restauré = vert. +Même classe enfin sur la **cross-cohérence du barème de commissions** (livrable +`crm/commissions`) — le CŒUR du module. La section « Cross-cohérence » de son README +énumère **à la main** deux ensembles data-derived + une propriété : les états +**brouillon exclus** « `lead/visite/devis/abandonné` » (README:47), les **champs de +base** « `montant_reservation`, `montant_contrat` » (README:50) et l'invariant « on +ne commissionne QUE des états **soumis** (`doc_status = 1`) » (README:46). Le bloc +racine ne gate que le **compte de tests** commissions (« 25 », via l'agrégat « Total +CRM ») ; ces ensembles n'étaient recomputés par AUCUN gate. Or ils sont +**data-derived** : les brouillons = `{update_value | doc_status == 0}` de +`crm/workflow_vente/out/workflow.json`, les champs de base = `{base_field}` de +`crm/commissions/out/commission_plan.json` (les deux byte-gatés par +`check_artifacts`). PROMOUVOIR un état brouillon en soumis, AJOUTER/RENOMMER un champ +de base ou ÉCHANGER un état ferait dériver la prose en silence pendant que l'artefact +dit autre chose — dérive qu'aucune suite `tests/` (qui teste des FONCTIONS +finance/résolution, pas la prose) n'attrape. On RECOMPUTE chaque ensemble depuis SON +artefact (zéro duplication du contrat du générateur · #6) et on exige que la prose +l'énumère **exactement** (absent ET en trop mordus via set-diff · accents/casse +normalisés). Cohérence croisée en bonus (mord un plan **internement incohérent** avec +le workflow) : tout `update_value` commissionné ∈ états soumis ET ∉ brouillons — +l'**invariant 3** du module, non vide. 5 morsures vérifiées : échange brouillon +(`abandonné→perdu`) · sous-ensemble des champs de base (retrait `montant_contrat`) · +énumération des brouillons supprimée (INTROUVABLE) · plan pointant un brouillon +(`reservation→lead`, invariant 3 cassé) · promotion de l'état `abandonne` en soumis +dans l'artefact (draft set change, « en trop » dans la prose) ; restauré = vert. + Un claim **absent** échoue aussi (la dérive de formulation qui ferait disparaître un chiffre est elle-même une régression de traçabilité). stdlib pur (bash/git/python3), zéro réseau. diff --git a/ci/check_readme_claims.sh b/ci/check_readme_claims.sh index cf8b836..612dc51 100755 --- a/ci/check_readme_claims.sh +++ b/ci/check_readme_claims.sh @@ -104,7 +104,7 @@ cd_repo_root # racine du dépôt, ou ROUGE honnête si hors arbre git (cf. lib. echo "== Intégrité des chiffres du README (valeur == artefact cité · #6) ==" python3 - <<'PY' -import json, re, subprocess, sys +import json, re, subprocess, sys, unicodedata D = "05_deliverables_mvp" FAIL = 0 @@ -1621,6 +1621,98 @@ if encf is not None: re.search(r"`([A-Z])\s*\+\s*tipoeCF\((\d+)\)\s*\+\s*" r"secuencia\((\d+)\)`", ecf_fi), False) +# ============================================================================ +# CRM/Commissions · barème vendeurs — la CROSS-COHÉRENCE (« cœur du livrable ») +# transcrite À LA MAIN dans le README sans AUCUN gate d'IDENTITÉ. +# ---------------------------------------------------------------------------- +# Le générateur commissions_gen émet out/commission_plan.json (byte-gaté par +# check_artifacts ⇒ faisant autorité) : chaque évènement porte `update_value` +# (l'état du pipeline qui PAIE) et `base_field` (le champ Currency de base). La +# section « Cross-cohérence » du README ÉNUMÈRE À LA MAIN deux ensembles +# data-derived + une propriété — le CŒUR du livrable : +# • README:47 « on ne commissionne pas un brouillon (lead/visite/devis/ +# abandonné) » — l'ensemble des états BROUILLON (doc_status=0 du workflow). +# • README:50 « base_field … (`montant_reservation`, `montant_contrat`) » — +# l'ensemble des champs de base cités par le plan. +# • README:46 « correspondre à un état soumis (doc_status = 1) » — INVARIANT 3 +# du module : on ne commissionne QUE des états soumis, jamais un brouillon. +# Le bloc racine ne gate QUE le COMPTE de tests commissions (« 25 », via l'agrégat +# « Total CRM »). Aucun gate ne comparait ces ENSEMBLES aux artefacts : PROMOUVOIR +# un état brouillon en soumis (abandonne → doc_status 1) le retire de l'ensemble +# brouillon pendant que la prose l'énumère encore ; AJOUTER un champ de base à un +# évènement (base_field montant_confotur) l'ajoute sans que la prose bouge ; +# RENOMMER un état passe aussi — « vert trompeur » qu'aucune suite tests/ (qui teste +# des FONCTIONS finance/résolution, pas la prose) n'attrape. Même patron que +# l'énumération des onglets Mobile, des transitions quatre-yeux CRM ou du format +# e-NCF fiscal. On RECOMPUTE chaque ensemble depuis SON artefact byte-gaté (zéro +# duplication du contrat du générateur #6) et on exige que la prose l'énumère +# EXACTEMENT (absent ET en trop mordus via set-diff · accents/casse normalisés). +# Cohérence croisée en bonus (mord un plan INTERNEMENT incohérent avec le workflow) : +# tout `update_value` commissionné ∈ états soumis (doc_status=1) ET ∉ brouillons — +# l'invariant 3 du module, non vide. Un claim absent échoue AUSSI (traçabilité). +# ============================================================================ +CM_RD = f"{D}/crm/commissions/README.md" +def _cm_acc(s): + return "".join(c for c in unicodedata.normalize("NFKD", s) + if unicodedata.category(c) != "Mn") +try: + cm_ev = load("crm/commissions/out/commission_plan.json")["evenements"] + cm_states = load("crm/workflow_vente/out/workflow.json")[0]["states"] + cm_base = {e["base_field"] for e in cm_ev} + cm_uv = {e["update_value"] for e in cm_ev} + cm_draft = {s["update_value"] for s in cm_states if str(s["doc_status"]) == "0"} + cm_subm = {s["update_value"] for s in cm_states if str(s["doc_status"]) == "1"} +except (OSError, KeyError, ValueError, IndexError, TypeError) as e: + bad(f"CRM commissions · artefact illisible/inattendu : {e}"); cm_ev = None +if cm_ev is not None: + # bonus : invariant 3 — tout état commissionné est SOUMIS (jamais brouillon), non vide + if not cm_uv: + bad("CRM commissions · aucun évènement commissionné (plan vide) → vérifier le barème") + elif not (cm_uv <= cm_subm): + bad(f"CRM commissions · incohérence plan↔workflow : update_values commissionnés " + f"{sorted(cm_uv - cm_subm)} PAS soumis (doc_status≠1) → invariant 3 cassé") + elif cm_uv & cm_draft: + bad(f"CRM commissions · incohérence plan↔workflow : {sorted(cm_uv & cm_draft)} " + f"commissionné(s) mais BROUILLON (doc_status=0) → invariant 3 cassé") + else: + good(f"CRM commissions · invariant 3 — {sorted(cm_uv)} tous soumis (doc_status=1), " + f"aucun brouillon") + cm_rd = read_norm(CM_RD) + if cm_rd is not None: + # 1) README:47 — ensemble des états BROUILLON exclus (doc_status=0 du workflow) + mb = re.search(r"brouillon \(([^)]*)\)", cm_rd) + if mb is None: + bad(f"CRM commissions · {CM_RD} — énumération des brouillons exclus " + f"INTROUVABLE (attendu {sorted(cm_draft)})") + else: + ps = {_cm_acc(t.strip().lower()) for t in re.split(r"[/,]", mb.group(1)) if t.strip()} + exp = {_cm_acc(x) for x in cm_draft} + if ps == exp: + good(f"CRM commissions · {CM_RD} — brouillons exclus {sorted(ps)} == " + f"workflow.json (doc_status=0)") + else: + miss = sorted(exp - ps); extra = sorted(ps - exp) + detail = (f" · absents={miss}" if miss else "") + \ + (f" · en trop={extra}" if extra else "") + bad(f"CRM commissions · {CM_RD} — énumère {sorted(ps)} MAIS brouillons " + f"workflow {sorted(exp)}{detail} → régénérer le README") + # 2) README:50 — ensemble des champs de base (base_field du plan) + mf = re.search(r"Currency réel\*\* du DocType Dossier Vente \(([^)]*)\)", cm_rd) + if mf is None: + bad(f"CRM commissions · {CM_RD} — énumération des champs de base " + f"INTROUVABLE (attendu {sorted(cm_base)})") + else: + bs = set(re.findall(r"`([a-z_]+)`", mf.group(1))) + if bs == cm_base: + good(f"CRM commissions · {CM_RD} — champs de base {sorted(bs)} == " + f"commission_plan.evenements[].base_field") + else: + miss = sorted(cm_base - bs); extra = sorted(bs - cm_base) + detail = (f" · absents={miss}" if miss else "") + \ + (f" · en trop={extra}" if extra else "") + bad(f"CRM commissions · {CM_RD} — énumère {sorted(bs)} MAIS plan " + f"{sorted(cm_base)}{detail} → régénérer le README") + sys.exit(1 if FAIL else 0) PY rc=$?