[DTP-Worker] Sprint 8 · buffer L75 · Domaine CRM/Commissions : la CROSS-COHÉRENCE du barème vendeurs (« cœur du livrable ») — états BROUILLON exclus (lead/visite/devis/abandonné), CHAMPS DE BASE (montant_reservation, montant_contrat) et INVARIANT 3 « on ne commissionne QUE des états soumis (doc_status=1) » — était énumérée À LA MAIN dans le README du module crm/commissions sans AUCUN gate d'IDENTITÉ. Le bloc racine ne gate que le COMPTE de tests (« 25 », via l'agrégat « Total CRM ») — surface distincte. Ces trois faits sont DATA-DERIVED : brouillons = {update_value | doc_status==0} de crm/workflow_vente/out/workflow.json (= {lead,visite,devis,abandonne}) · champs de base = {base_field} des évènements de crm/commissions/out/commission_plan.json · états soumis = {update_value | doc_status==1} — les deux artefacts byte-gatés par check_artifacts. Aucun gate ne comparait ces ENSEMBLES à la prose : 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 → nouveau bloc recomputant chaque ENSEMBLE depuis SON artefact byte-gaté (zéro duplication du contrat du générateur #6) et exigeant 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 ET ∉ brouillons — l'INVARIANT 3 du module, non vide. Un claim absent échoue AUSSI. 5 morsures vérifiées : échange brouillon (abandonné→perdu) capté (absents=[abandonne] en trop=[perdu]) · sous-ensemble champs de base (retrait montant_contrat) capté · énumération des brouillons supprimée (INTROUVABLE) · plan pointant un brouillon (reservation→lead, invariant 3 cassé) capté · promotion de l'état abandonne en soumis dans l'artefact (draft set change, « en trop » dans la prose) capté ; restauré = green : brouillons [abandonne,devis,lead,visite] == workflow (doc_status=0) · champs de base == plan · invariant 3 vert. État courant : aucun ensemble périmé (anti-invention #6, rien à réécrire) — le défaut est la surface ungated. ci/README.md (table + détail) mis à jour · 7 gates re-verts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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=$?
|
||||
|
||||
Reference in New Issue
Block a user