6.3 KiB
NUKE-3 — Plan de sprint court
3 Objectifs
- Réduire le taux REVIEW sur les dossiers sans DP : 43/43 → cible < 50% REVIEW
- Améliorer la qualité du pool : éliminer le bruit OCR et fusionner les doublons
- Activer le signal synthèse : rendre motif_align opérant même sur les dossiers trackare
5 Patchs proposés (ordre d'impact)
Patch 1 — Dedup par code dans build_candidates() ⭐⭐⭐
Symptôme : Même code CIM-10 apparaît 2× (edsnlp + regex) → le candidat se bat contre lui-même, delta artificiellement réduit.
Patch minimal : src/medical/dp_selector.py, fonction build_candidates()
# Après la boucle de construction des candidats :
# Dedup par code : garder le meilleur section_strength, ajouter bonus multi-source
seen: dict[str, DPCandidate] = {}
for c in candidates:
if not c.code:
continue
if c.code in seen:
existing = seen[c.code]
existing.num_occurrences += 1
if c.section_strength > existing.section_strength:
existing.section_strength = c.section_strength
existing.source = c.source
else:
seen[c.code] = c
candidates = list(seen.values())
# Réindexer
for i, c in enumerate(candidates):
c.index = i
Pourquoi ça aide : Les 4 dossiers avec doublons (I26.9, K81.0, D69.6, J18.9) gagnent un candidat fusionné plus fort. Le bonus occurrences existant (+1 ou +2) s'active déjà sur num_occurrences.
Test : test_dedup_same_code_merged() — 2 candidats avec même code, sources différentes → 1 seul candidat avec meilleur section_strength.
Effort : 1h
Patch 2 — Filtre bruit OCR dans build_candidates() ⭐⭐⭐
Symptôme : Candidats avec texte OCR corrompu ("C : 9.4", "C omprend décollement de la (d") polluent le pool et consomment des places dans top_k=7.
Patch minimal : src/medical/dp_selector.py, dans build_candidates()
MIN_TERM_WORDS = 2
def _is_ocr_noise(text: str) -> bool:
"""Rejette les candidats dont le texte est du bruit OCR."""
clean = text.strip()
if len(clean) < 4:
return True
words = clean.split()
if len(words) < MIN_TERM_WORDS:
return True
# Ratio de caractères non-alpha suspect
alpha = sum(1 for ch in clean if ch.isalpha())
if alpha / max(len(clean), 1) < 0.5:
return True
return False
Pourquoi ça aide : Réduit le pool de 7 à ~4-5 candidats pertinents, améliore le delta.
Test : test_ocr_noise_excluded() — candidat "C : 9.4" exclu du pool.
Effort : 1h
Patch 3 — Synthèse depuis top-level JSON + sections trackare ⭐⭐
Symptôme : 100% des REVIEW ont synthèse vide → motif_align ne fonctionne jamais. Le JSON a sejour.motif mais build_synthese() ne lit que sections.motif_hospitalisation.
Patch minimal : src/medical/dp_selector.py, fonction build_synthese()
def build_synthese(dossier, parsed_data):
sections = parsed_data.get("sections", {})
motif = sections.get("motif_hospitalisation", "")
conclusion = sections.get("conclusion", "")
# Fallback : motif depuis le séjour (trackare)
if not motif and dossier.sejour.motif:
motif = dossier.sejour.motif
return {"motif": motif, "conclusion": conclusion, ...}
Pourquoi ça aide : Active le bonus motif_align (+2) sur les trackare qui stockent le motif dans sejour.motif, discriminant les candidats.
Test : test_synthese_fallback_sejour_motif() — synthèse avec motif depuis séjour.
Effort : 30min
Patch 4 — Abaisser DELTA_CONFIRMED de 3.0 à 2.0 ⭐⭐
Symptôme : 13/43 REVIEW ont delta 2.0-2.9 — cas où le pré-ranker a une préférence nette mais pas assez pour le seuil actuel. Exemple : T83.5 (4.0) vs R50.9 (2.0) = delta 2.0 → REVIEW, alors que le symptôme (R50.9) est clairement un mauvais DP.
Patch minimal : src/medical/dp_selector.py
DELTA_CONFIRMED = 2.0 # Était 3.0
Pourquoi ça aide : +13 CONFIRMED (30% des REVIEW), mais uniquement si les gardes A1/A2/A3 valident. Le hardening empêche les faux positifs.
Test : Adapter les fixtures existantes (dp_acute_vs_comorbidity.json utilise un delta attendu).
Effort : 30min (+ vérification non-régression)
Patch 5 — DAS order bonus (premier listé = plus saillant) ⭐
Symptôme : En cas d'ex-aequo total (15 cas), il n'y a aucun signal pour départager. Or l'ordre des DAS dans le document médical n'est pas aléatoire — les premiers listés sont souvent les plus pertinents.
Patch minimal : src/medical/dp_selector.py, dans score_candidates()
# 9. Bonus d'ordre (premier DAS listé = +1)
if c.index == 0:
score += 1
details["first_listed"] = 1
elif c.index == 1:
score += 0.5
details["second_listed"] = 0.5
Pourquoi ça aide : Brise les ex-aequo dans 15 cas (36% des REVIEW) avec un signal faible mais non-arbitraire. Le premier DAS listé reflète l'ordre du document source.
Test : test_first_listed_bonus() — 2 candidats identiques, le premier listé gagne.
Effort : 30min
Ordre d'exécution
| # | Patch | Effort | Impact estimé | Pré-requis |
|---|---|---|---|---|
| 1 | Dedup par code | 1h | 4 dossiers améliorés | Aucun |
| 2 | Filtre bruit OCR | 1h | ~5 dossiers pool nettoyé | Aucun |
| 3 | Synthèse fallback | 30min | Jusqu'à 43 dossiers (si motif trackare dispo) | Vérifier séjour.motif |
| 4 | DELTA 3.0 → 2.0 | 30min | +13 CONFIRMED | Patches 1+2 (pool propre) |
| 5 | DAS order bonus | 30min | Brise 15 ex-aequo | Après patch 4 |
Total estimé : 3h30
Critères d'acceptation (KPI)
| KPI | Avant | Cible |
|---|---|---|
| REVIEW rate (sans-DP) | 100% (43/43) | < 50% |
| CONFIRMED + evidence | 100% | 100% (maintenu) |
| DP symptôme R* (CONFIRMED) | 0% | < 5% |
| DP comorbidité (CONFIRMED) | 0% | < 3% |
| Candidats bruit OCR dans pool | ~5% | 0% |
| Ex-aequo (delta=0) | 36% (15/43) | < 15% |
Prochaines étapes hors sprint
- Gold standard CRH : sélectionner 20 CRH avec DP expert pour mesurer
confirmed_accuracy - Benchmark LLM ON : relancer le pipeline avec
T2A_DP_RANKER_LLM=1sur les 43 REVIEW - Extraction synthèse trackare : parser le motif d'hospitalisation depuis les PDF trackare
Plan rédigé le 2026-02-24 — benchmark offline sur 249 dossiers