chore: add .gitignore
This commit is contained in:
171
docs/NUKE3_NEXT_STEPS.md
Normal file
171
docs/NUKE3_NEXT_STEPS.md
Normal file
@@ -0,0 +1,171 @@
|
||||
# NUKE-3 — Plan de sprint court
|
||||
|
||||
## 3 Objectifs
|
||||
|
||||
1. **Réduire le taux REVIEW sur les dossiers sans DP** : 43/43 → cible < 50% REVIEW
|
||||
2. **Améliorer la qualité du pool** : éliminer le bruit OCR et fusionner les doublons
|
||||
3. **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()`
|
||||
```python
|
||||
# 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()`
|
||||
```python
|
||||
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()`
|
||||
```python
|
||||
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`
|
||||
```python
|
||||
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()`
|
||||
```python
|
||||
# 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=1` sur 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*
|
||||
Reference in New Issue
Block a user