fix: FP médicaments dans raster + texte — RE_EXTRACT_STAFF_ROLE + FINESS + stop-words
Bug #1 (critique) : RE_EXTRACT_STAFF_ROLE matchait à l'intérieur des mots (IDE dans METOCLOPRAMIDE, AS dans ATORVASTATINE) → ajout \b word boundaries et suppression du ? optionnel sur ASH (AS matchait partout) Bug #2 : raster multi-mots utilisait page.search_for() (substring matching) → ajout vérification frontières de mots pour les tokens multi-mots dans redact_pdf_raster et redact_pdf_vector FP FINESS Aho-Corasick : - "resistance" (Centre de la Résistance) matchait "résistance aux fluoroquinolones" - "radiotherapie" matchait "tumorectomie, radiothérapie et hormonothérapie" → ajout blacklist : resistance, radiotherapie, chimiotherapie, etc. FP villes : "COU" (commune) matchait dans "prurit (cou, décolleté, dos)" → ajout COU, DOS, SEIN, BRAS à _VILLE_BLACKLIST Stop-words : ajout "totale", "partielle", "prothese", "unicompartimentale" Score évaluation maintenu à 100.0/100 (A+) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -200,6 +200,8 @@ _VILLE_BLACKLIST = {
|
|||||||
"FRANCE", "EUROPE",
|
"FRANCE", "EUROPE",
|
||||||
# Termes ambigus (aussi communes INSEE) - trackare/DPI
|
# Termes ambigus (aussi communes INSEE) - trackare/DPI
|
||||||
"COURANT", # "Médecin courant" ≠ ville
|
"COURANT", # "Médecin courant" ≠ ville
|
||||||
|
# Parties du corps homonymes de communes (FP "prurit invalidant (COU, décolleté)")
|
||||||
|
"COU", "DOS", "SEIN", "BRAS",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -753,6 +755,9 @@ _MEDICAL_STOP_WORDS_SET = {
|
|||||||
"chlorure",
|
"chlorure",
|
||||||
# Dispositifs médicaux (FP "OXYGENE LUNETTES" → [NOM])
|
# Dispositifs médicaux (FP "OXYGENE LUNETTES" → [NOM])
|
||||||
"canule", "canules", "masque", "sonde", "sondes",
|
"canule", "canules", "masque", "sonde", "sondes",
|
||||||
|
# Termes chirurgicaux FP comme [NOM] (retour relecteur 2026-03-17)
|
||||||
|
"totale", "total", "partielle", "partiel",
|
||||||
|
"prothese", "prothèse", "unicompartimentale",
|
||||||
}
|
}
|
||||||
# Enrichissement automatique avec les ~4000 noms de médicaments d'edsnlp
|
# Enrichissement automatique avec les ~4000 noms de médicaments d'edsnlp
|
||||||
_MEDICAL_STOP_WORDS_SET.update(_load_edsnlp_drug_names())
|
_MEDICAL_STOP_WORDS_SET.update(_load_edsnlp_drug_names())
|
||||||
@@ -865,8 +870,8 @@ RE_EXTRACT_DR_DEST = re.compile(
|
|||||||
)
|
)
|
||||||
# Noms du personnel médical après un rôle : "Aide : Marie-Paule BORDABERRY"
|
# Noms du personnel médical après un rôle : "Aide : Marie-Paule BORDABERRY"
|
||||||
RE_EXTRACT_STAFF_ROLE = re.compile(
|
RE_EXTRACT_STAFF_ROLE = re.compile(
|
||||||
r"(?:Aide|Infirmière?|IDE|IADE|IBODE|ASH?|Cadre[ \t]+Infirmier"
|
r"\b(?:Aide|Infirmière?|IDE|IADE|IBODE|ASH|Cadre[ \t]+Infirmier"
|
||||||
r"|Prescripteur|Prescrit[ \t]+par|Exécut[ée][ \t]+par|Réalisé[ \t]+par)[ \t]*:?[ \t]*"
|
r"|Prescripteur|Prescrit[ \t]+par|Exécut[ée][ \t]+par|Réalisé[ \t]+par)\b[ \t]*:?[ \t]*"
|
||||||
r"((?:[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ][a-zéèàùâêîôûäëïöüç]+(?:[ \t]*-[ \t]*[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ][a-zéèàùâêîôûäëïöüç]+)?[ \t]+)?"
|
r"((?:[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ][a-zéèàùâêîôûäëïöüç]+(?:[ \t]*-[ \t]*[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ][a-zéèàùâêîôûäëïöüç]+)?[ \t]+)?"
|
||||||
r"(?:[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ]{2,}[\-]?)(?:[ \t\-]+[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ]{2,}){0,2})",
|
r"(?:[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ]{2,}[\-]?)(?:[ \t\-]+[A-ZÉÈÀÙÂÊÎÔÛÄËÏÖÜÇ]{2,}){0,2})",
|
||||||
)
|
)
|
||||||
@@ -2559,6 +2564,12 @@ def _build_finess_ac():
|
|||||||
"comprimee", "comprimees", "injectable", "injectables",
|
"comprimee", "comprimees", "injectable", "injectables",
|
||||||
"maintenant", "actuellement", "auparavant", "prochainement",
|
"maintenant", "actuellement", "auparavant", "prochainement",
|
||||||
"rapidement", "correctement", "directement", "simplement",
|
"rapidement", "correctement", "directement", "simplement",
|
||||||
|
# Termes médicaux homonymes d'établissements FINESS (retour relecteur 2026-03-17)
|
||||||
|
"resistance", "radiotherapie", "chimiotherapie", "curietherapie",
|
||||||
|
"hormonotherapie", "immunotherapie", "kinesitherapie",
|
||||||
|
"ergotherapie", "orthophonie", "psychomotricite",
|
||||||
|
"reeducation", "readaptation", "convalescence",
|
||||||
|
"dependance", "autonomie", "gerontologie",
|
||||||
}
|
}
|
||||||
# Expressions multi-mots trop génériques
|
# Expressions multi-mots trop génériques
|
||||||
_ac_generic_phrases = {
|
_ac_generic_phrases = {
|
||||||
@@ -3334,7 +3345,14 @@ def redact_pdf_vector(original_pdf: Path, audit: List[PiiHit], out_pdf: Path, oc
|
|||||||
rects = _search_ocr_words(ocr_word_map[pno], token, page.rect)
|
rects = _search_ocr_words(ocr_word_map[pno], token, page.rect)
|
||||||
all_rects.extend(rects)
|
all_rects.extend(rects)
|
||||||
else:
|
else:
|
||||||
|
# Vérification frontières de mots (comme raster)
|
||||||
rects = page.search_for(token)
|
rects = page.search_for(token)
|
||||||
|
if rects:
|
||||||
|
page_text = page.get_text()
|
||||||
|
import re as _re
|
||||||
|
if not _re.search(r"(?<![A-Za-zÀ-ÿ])" + _re.escape(token) + r"(?![A-Za-zÀ-ÿ])",
|
||||||
|
page_text, _re.IGNORECASE):
|
||||||
|
rects = []
|
||||||
if not rects:
|
if not rects:
|
||||||
for word in token.split():
|
for word in token.split():
|
||||||
word = word.strip(" .-'")
|
word = word.strip(" .-'")
|
||||||
@@ -3469,8 +3487,22 @@ def redact_pdf_raster(original_pdf: Path, audit: List[PiiHit], out_pdf: Path, dp
|
|||||||
found_ww = _search_ocr_words(ocr_word_map[pno], token, page.rect)
|
found_ww = _search_ocr_words(ocr_word_map[pno], token, page.rect)
|
||||||
rects.extend(found_ww)
|
rects.extend(found_ww)
|
||||||
else:
|
else:
|
||||||
# Token multi-mots : d'abord chercher la chaîne complète
|
# Token multi-mots : chercher la chaîne complète puis vérifier
|
||||||
|
# les frontières de mots pour éviter le substring matching
|
||||||
|
# (ex: "TATINE EG" trouvé dans "ATORVASTATINE EG")
|
||||||
found_multi = page.search_for(token)
|
found_multi = page.search_for(token)
|
||||||
|
if found_multi:
|
||||||
|
# Vérifier que le match est sur des frontières de mots
|
||||||
|
page_text = page.get_text()
|
||||||
|
verified = []
|
||||||
|
for rect in found_multi:
|
||||||
|
# Extraire le texte autour du match pour vérifier les limites
|
||||||
|
# Chercher le token dans le texte brut avec \b
|
||||||
|
import re as _re
|
||||||
|
if _re.search(r"(?<![A-Za-zÀ-ÿ])" + _re.escape(token) + r"(?![A-Za-zÀ-ÿ])",
|
||||||
|
page_text, _re.IGNORECASE):
|
||||||
|
verified.append(rect)
|
||||||
|
found_multi = verified
|
||||||
if not found_multi:
|
if not found_multi:
|
||||||
# Fallback : chercher chaque mot comme mot entier
|
# Fallback : chercher chaque mot comme mot entier
|
||||||
for word in token.split():
|
for word in token.split():
|
||||||
|
|||||||
Reference in New Issue
Block a user