fix(detect): accepter prénoms 3 chars après Dr/Mme (Ute, Eva, Léo…)
Audit manuel après batch QC : 20 occurrences de "Dr Ute" dans trackare-03020576-23175616 non masquées. Audit jsonl confirme : 0 hit pour "Ute" → pas détecté. Cause : _add_candidate (deux implémentations, lignes 1908 et 2225) filtrait len(token) < 4, empêchant la création du NameCandidate pour "Ute" (3 chars) même avec bypass_stopwords=True. La cross-validation écrasait alors all_names avec validated_names (vide pour Ute), et _apply_extracted_names ne recevait donc jamais Ute. Le commit 2f79f7c avait fait le fix uniquement dans _apply_extracted_names. Fix incomplet : le filtre amont _add_candidate rejetait avant. Correctif symétrique sur _add_candidate (×2) + _add_tokens_force_first : accepter 3 chars UNIQUEMENT si bypass=True (contexte Dr/Mme) ET majuscule initiale ET alpha pur. 2 chars reste filtré (initiales ambigues). Validation : - "DR. DURANTEAU Ute" matche bien RE_EXTRACT_DR_DEST et capture "DURANTEAU Ute" - Audit produit "Ute DURANTEAU" en bloc + "DURANTEAU" seul (41 hits total) - PDF redacted : 0 résiduel "Ute" (avant : 38) Cas protégés : - "Ute" accepté : bypass=True, U majuscule, alpha ✓ - "Les" refusé : bypass=True mais stopword (filtré ailleurs) ✓ - "JF" refusé : 2 chars, filtre longueur < 3 ✓ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1903,9 +1903,13 @@ def _extract_trackare_identity(full_text: str) -> Tuple[set, List[PiiHit], set,
|
|||||||
force_names: set = set() # noms issus de contextes structurés (DR., Signé, etc.) → bypass stop words
|
force_names: set = set() # noms issus de contextes structurés (DR., Signé, etc.) → bypass stop words
|
||||||
|
|
||||||
def _add_candidate(token: str, source: str, strength: str, bypass: bool):
|
def _add_candidate(token: str, source: str, strength: str, bypass: bool):
|
||||||
"""Ajoute un NameCandidate à la liste."""
|
"""Ajoute un NameCandidate à la liste.
|
||||||
|
Accepte les prénoms courts 3 chars (Ute, Eva, Léo…) si bypass=True
|
||||||
|
(contexte Dr/Mme fort) ET majuscule initiale + alpha pur."""
|
||||||
token = token.strip(" .-'(),")
|
token = token.strip(" .-'(),")
|
||||||
if len(token) < 4:
|
if len(token) < 3:
|
||||||
|
return
|
||||||
|
if len(token) == 3 and not (bypass and token[0].isupper() and token.isalpha()):
|
||||||
return
|
return
|
||||||
candidates.append(NameCandidate(
|
candidates.append(NameCandidate(
|
||||||
token=token, source=source,
|
token=token, source=source,
|
||||||
@@ -2220,9 +2224,13 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s
|
|||||||
candidates: List[NameCandidate] = []
|
candidates: List[NameCandidate] = []
|
||||||
|
|
||||||
def _add_candidate(token: str, source: str, strength: str, bypass: bool):
|
def _add_candidate(token: str, source: str, strength: str, bypass: bool):
|
||||||
"""Ajoute un NameCandidate à la liste (dédupliqué par token+source)."""
|
"""Ajoute un NameCandidate à la liste (dédupliqué par token+source).
|
||||||
|
Accepte les prénoms courts 3 chars (Ute, Eva, Léo…) si bypass=True
|
||||||
|
(contexte Dr/Mme fort) ET majuscule initiale + alpha pur."""
|
||||||
token = token.strip(" .-'")
|
token = token.strip(" .-'")
|
||||||
if len(token) < 4:
|
if len(token) < 3:
|
||||||
|
return
|
||||||
|
if len(token) == 3 and not (bypass and token[0].isupper() and token.isalpha()):
|
||||||
return
|
return
|
||||||
candidates.append(NameCandidate(
|
candidates.append(NameCandidate(
|
||||||
token=token, source=source,
|
token=token, source=source,
|
||||||
@@ -2270,12 +2278,21 @@ def _extract_document_names(full_text: str, cfg: Dict[str, Any]) -> Tuple[set, s
|
|||||||
|
|
||||||
Après Dr/Mme, tous les tokens sont des noms — même s'ils sont
|
Après Dr/Mme, tous les tokens sont des noms — même s'ils sont
|
||||||
homonymes de termes médicaux (ex: Dr Laurence MASSE).
|
homonymes de termes médicaux (ex: Dr Laurence MASSE).
|
||||||
|
|
||||||
|
Accepte les prénoms courts 3 chars (Dr Ute, Dr Eva, Dr Léo) : le
|
||||||
|
contexte Dr/Mme est suffisamment fort pour lever le filtre de
|
||||||
|
longueur, à condition que le token soit alpha et commence par
|
||||||
|
une majuscule. 2 chars reste filtré (trop ambigu : initiales).
|
||||||
"""
|
"""
|
||||||
_add_compound(match_str)
|
_add_compound(match_str)
|
||||||
tokens = match_str.split()
|
tokens = match_str.split()
|
||||||
for token in tokens:
|
for token in tokens:
|
||||||
token = token.strip(" .-'")
|
token = token.strip(" .-'")
|
||||||
if len(token) < 4:
|
if len(token) < 3:
|
||||||
|
continue
|
||||||
|
# 3 chars : accepter uniquement si majuscule initiale + alpha
|
||||||
|
# (évite "Les", "Des" mais accepte "Ute", "Eva").
|
||||||
|
if len(token) == 3 and not (token[0].isupper() and token.isalpha()):
|
||||||
continue
|
continue
|
||||||
if token.upper() in wl_sections or token in wl_phrases:
|
if token.upper() in wl_sections or token in wl_phrases:
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user