---
from: qwen
to: dom
date: 2026-05-29T10:30:00+02:00
topic: analyse-regression-grand
status: open
references:
  - file: data/stopwords_manuels.txt:549
  - file: anonymizer_core_refactored_onnx.py:2725
  - file: data/insee/noms_famille_frequents.txt:43233
  - commit: 13730d1
  - tests: tests/unit/test_leak_scanner.py
priority: high
---

# Analyse régression — fuite "GRAND" (17 occurrences)

## Contexte

L'évaluation qualité sur le commit `13730d1` détecte **17 occurrences de "GRAND"** non masquées dans le document `trackare-05012965-23060770_05012965_23060770.pseudonymise.txt`. Score qualité : 99.8/100 (régression de -0.3 sur leak_score vs baseline).

---

## Cause racine identifiée

**Le mot `"grand"` est présent dans `data/stopwords_manuels.txt` à la ligne 549.**

Ce mot est chargé dans `_MEDICAL_STOP_WORDS_SET` (ligne 474-475 du core).

### Pourquoi "grand" a été ajouté aux stopwords

Probablement pour filtrer des expressions médicales comme "grand axe", "grande courbure", "grande taille" — termes anatomiques légitimes qui ne doivent pas être masqués.

### Pourquoi c'est un problème

**"GRAND" est aussi un nom de famille INSEE valide et courant** :
- Présent dans `data/insee/noms_famille_france.txt` (ligne 97117)
- Présent dans `data/insee/noms_famille_frequents.txt` (ligne 43233)

---

## Mécanisme de la fuite

Le patient s'appelle **Romain BILLON-GRAND**, et le médecin traitant est **DR. [NOM]-GRAND**.

Dans le fichier de sortie, les 17 occurrences non masquées apparaissent sous deux formes :

1. **`DR. [NOM]-GRAND`** — nom du docteur dans les en-têtes de prescriptions
2. **`[NOM]-GRAND`** — dans les tableaux de prescriptions

### Pourquoi le nom composé "BILLON-GRAND" est masqué mais "GRAND" seul ne l'est pas

Le pipeline traite "BILLON-GRAND" comme un **token unique** (pas de split sur le tiret dans `_extract_trackare_identity._add_name`). Le nom composé est détecté via le contexte `DR.` et masqué correctement.

Mais dans les tableaux Trackare, le formatage fait que **"GRAND" se retrouve seul sur une ligne**, séparé de "[NOM]-" par un saut de ligne :

```
DR. [NOM]-
GRAND
```

Quand le traitement ligne par ligne (`_mask_line_by_line`) rencontre "GRAND" seul :

```python
# Ligne ~2725 du core
if tok.lower() in _MEDICAL_STOP_WORDS_SET:
    continue  # ← "grand" est skipé, jamais proposé au masquage
```

**"grand" étant dans les stopwords, il est filtré et jamais masqué.**

---

## Les 2 occurrences de "grande" — faux positif

Le document contient aussi 2 occurrences de `"grande"` (lignes 2969, 2974) :
- `plus grande epaisseur` (radiologie/anatomie)
- `plus grande epaisseur` (radiologie)

Ces occurrences sont **légitimes** — ce sont des adjectifs médicaux. Elles ne doivent **PAS** être masquées.

---

## Fix proposé

### Option 1 (simple) : retirer "grand" des stopwords

**Action** : supprimer la ligne 549 de `data/stopwords_manuels.txt`.

**Impact** : "GRAND" en MAJUSCULES sera détecté comme nom INSEE et masqué. "grande" en minuscules dans un contexte médical ("plus grande epaisseur") ne sera pas affecté car le matching INSEE se fait sur les tokens en MAJUSCULES.

**Risque** : "grand" en minuscule dans d'autres contextes pourrait être détecté comme faux positif (ex: "grand axe"). Mais le mécanisme de validation croisée (`_cross_validate_name_candidates`) exige un contexte fort (DR., MAJUSCULES, etc.) pour masquer — un "grand" isolé en minuscule ne sera pas masqué.

**Recommandation** : ✅ **Option 1 retenue**. C'est le fix le plus sûr.

### Option 2 (complexe) : stopwords contextuels

Transformer les stopwords single-word en patterns contextuels :
- "grand" → ne filtrer que si suivi de "axe", "courbure", "epaisseur", etc.
- Sinon, laisser le pipeline INSEE faire son travail

**Risque** : complexité accrue, maintenance difficile. Pas adapté au sprint MVP.

### Option 3 (hybride) : whitelist/blacklist INSEE

Ajouter une couche qui dit : "si un token est dans INSEE ET dans les stopwords, la priorité INSEE l'emporte".

**Impact** : changement de logique dans `_cross_validate_name_candidates`. Risque de faux positifs sur d'autres mots ambigus.

---

## Tests à ajouter

```python
# tests/unit/test_c8_grand_regression.py

def test_grand_insee_name_is_masked():
    """GRAND (nom INSEE) en MAJUSCULES doit être masqué même si présent dans stopwords."""
    ...

def test_grande_medical_not_masked():
    """'plus grande epaisseur' ne doit pas être masqué (adjectif médical)."""
    ...

def test_stopword_no_longer_blocks_insee_names():
    """Après retrait de 'grand' des stopwords, les noms INSEE ambigus sont traités."""
    ...
```

---

## Résumé

| Aspect | Détail |
|---|---|
| **Cause racine** | `"grand"` dans `data/stopwords_manuels.txt:549` filtre les noms INSEE "GRAND" |
| **Mécanisme** | `_mask_line_by_line` skip les tokens dont `.lower()` est dans `_MEDICAL_STOP_WORDS_SET` |
| **Impact** | 17 fuites réelles (nom propre BILLON-GRAND) |
| **Fix recommandé** | Retirer `"grand"` de `data/stopwords_manuels.txt` — 1 ligne à supprimer |
| **Risque du fix** | Minimal — le pipeline INSEE exige un contexte fort pour masquer |
| **Effort** | 5 minutes (1 ligne + 3 tests) |

---

— Qwen
