[DTP-Worker 20260812_093114] fix correctness · financement_bancaire gate — un dossier incomplet pouvait afficher percent 100.0 (barre pleine ✓) tout en BLOQUANT
Défaut latent, byte-repro-aveugle : _cond_apport arrondit percent à 1 décimale (round) tandis que ok est un test exact. Un versement dans la bande [99.95%,100%[ (ex. 19 999/20 000 USD = 99.995%) remonte à percent:100.0 alors que ok=False, remaining=1 USD → barre frontend « 100% ✓ » qui refuse la soumission + raison auto-contradictoire « reste 1 USD · 100.0% ». Seul _cond_apport atteignable (montant continu) ; documents/autorisations = ratios entiers sur listes 13/4, bande inatteignable. Fix chirurgical : garde `if not ok and pct>=100.0: pct=99.9` → invariant d'affichage percent==100.0 ⟺ ok. Échantillon commité à 67.8% (hors bande) → gate_status_example.json/out rebuild BYTE-IDENTIQUE. Test à dents test_apport_quasi_complet_ninclut_pas_100pct (mordu sans le fix). Cascade +1 test : régression 633→634/616→617, plan+MANIFEST+quality_report régénérés, fiches QA/Backend/CRM re-dérivées. run_ci 33/0/0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -94,9 +94,18 @@ def _cond_apport(dossier: dict, cfg: GateConfig) -> dict:
|
||||
required = apport_requis_usd(dossier.get("prix_usd", 0), residence, cfg)
|
||||
paid = apport_verse_usd(dossier)
|
||||
remaining = round(max(0.0, required - paid), 2)
|
||||
ok = paid + 1e-9 >= required
|
||||
pct = _round1(min(100.0, (paid / required * 100.0) if required > 0 else 100.0))
|
||||
# Un versement dans la bande [99.95 %, 100 %[ (p. ex. 19 999/20 000 USD)
|
||||
# remonterait à 100.0 par l'arrondi 1-décimale, alors que le gate BLOQUE
|
||||
# (ok=False, remaining>0). La barre de progression afficherait « 100 % ✓ »
|
||||
# tout en refusant la soumission, et la raison lirait « reste 1 USD · 100 %».
|
||||
# Invariant d'affichage : percent == 100.0 ⟺ ok — un dossier incomplet
|
||||
# plafonne à 99.9 (plus grande valeur 1-décimale strictement < 100).
|
||||
if not ok and pct >= 100.0:
|
||||
pct = 99.9
|
||||
return {
|
||||
"ok": paid + 1e-9 >= required,
|
||||
"ok": ok,
|
||||
"required": required,
|
||||
"paid": paid,
|
||||
"remaining": remaining,
|
||||
|
||||
@@ -118,6 +118,21 @@ class GateCheckTest(unittest.TestCase):
|
||||
self.assertTrue(status["conditions"]["apport_initial_complet"]["ok"])
|
||||
self.assertEqual(status["conditions"]["apport_initial_complet"]["percent"], 100.0)
|
||||
|
||||
def test_apport_quasi_complet_ninclut_pas_100pct(self):
|
||||
# Bande [99.95 %, 100 %[ : l'arrondi 1-décimale de 99.995 % remonte à
|
||||
# 100.0, mais le gate BLOQUE (ok=False, reste 1 USD). Un dossier
|
||||
# incomplet ne doit JAMAIS afficher 100 % (barre pleine ⇔ soumissible).
|
||||
d = _dossier_complet(self.cfg, self.spec)
|
||||
d["paiements"] = [{"montant_verse_usd": 19999}] # 20 000 requis → 99.995 %
|
||||
c = gate.gate_status(d, self.cfg)["conditions"]["apport_initial_complet"]
|
||||
self.assertFalse(c["ok"])
|
||||
self.assertAlmostEqual(c["remaining"], 1.0)
|
||||
self.assertLess(c["percent"], 100.0)
|
||||
self.assertEqual(c["percent"], 99.9)
|
||||
# La raison de blocage ne doit pas dire « 100 % ».
|
||||
_, reasons = gate.can_submit_dossier(d, self.cfg)
|
||||
self.assertTrue(any("Apport initial incomplet" in r and "100" not in r for r in reasons))
|
||||
|
||||
def test_apport_manquant_bloque(self):
|
||||
d = _dossier_complet(self.cfg, self.spec)
|
||||
d["paiements"] = [{"montant_verse_usd": 19999}]
|
||||
|
||||
@@ -450,7 +450,7 @@
|
||||
"criterion": "TESTS",
|
||||
"weight": 25,
|
||||
"passed": true,
|
||||
"evidence": "35 méthodes test_* dans 1 fichier(s) (seuil 8)"
|
||||
"evidence": "36 méthodes test_* dans 1 fichier(s) (seuil 8)"
|
||||
},
|
||||
{
|
||||
"criterion": "CLI",
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"version": "1.0",
|
||||
"verdict": "PASS",
|
||||
"suites": 24,
|
||||
"test_methods": 633,
|
||||
"test_methods": 634,
|
||||
"coverage_ok": true,
|
||||
"artifacts": [
|
||||
"regression_plan.json"
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
"in_gate": true,
|
||||
"has_tests_dir": true,
|
||||
"test_files": 1,
|
||||
"test_methods": 35
|
||||
"test_methods": 36
|
||||
},
|
||||
{
|
||||
"id": "crm.workflow_vente",
|
||||
@@ -295,7 +295,7 @@
|
||||
"totals": {
|
||||
"suites": 24,
|
||||
"test_files": 24,
|
||||
"test_methods": 633,
|
||||
"test_methods": 634,
|
||||
"min_methods": 10,
|
||||
"under_threshold": 0
|
||||
},
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
{
|
||||
"id": "crm.financement_bancaire",
|
||||
"path": "crm/financement_bancaire",
|
||||
"expected_methods": 35,
|
||||
"ran": 35,
|
||||
"passed": 35,
|
||||
"expected_methods": 36,
|
||||
"ran": 36,
|
||||
"passed": 36,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0,
|
||||
@@ -296,8 +296,8 @@
|
||||
"suites": 24,
|
||||
"green": 24,
|
||||
"red": 0,
|
||||
"ran": 633,
|
||||
"passed": 616,
|
||||
"ran": 634,
|
||||
"passed": 617,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 17
|
||||
|
||||
Reference in New Issue
Block a user