fix: anonymisation — sur-anonymisation + fuites PHI + patterns sécurisés
- DR_NAME_PATTERN limité à 2 mots (évite capture "CHARLANNE Traitements")
- CIVILITE_NAME_PATTERN et DESTINATAIRE_PATTERN : chaque mot doit commencer
par majuscule (évite capture de phrases entières comme noms)
- DATE_NAISSANCE_PATTERN : colon optionnel après "le" ("Né(e) le : DD/MM/YYYY")
- N_CSULT_PATTERN ajouté pour numéros de consultation anesthésie
- CONTACT_RELATION_PATTERN : +15 relations familiales (Neveu, Nièce, Oncle...)
- MEDICAL_TERMS_WHITELIST : +30 termes hospitaliers (scanner, traitement,
viscerale, radiologie, consultation, etc.)
- FRENCH_STOP_WORDS : +20 mots courts (fort, aide, suite, avant, etc.)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,15 @@ MEDICAL_TERMS_WHITELIST = {
|
|||||||
"coordonnateur", "fédération", "federation",
|
"coordonnateur", "fédération", "federation",
|
||||||
"institut", "cancérologie",
|
"institut", "cancérologie",
|
||||||
"palais",
|
"palais",
|
||||||
|
# Termes médicaux/hospitaliers à risque de sur-anonymisation
|
||||||
|
"traitement", "traitements", "scanner", "imagerie",
|
||||||
|
"viscerale", "viscérale", "thoracique", "abdominale",
|
||||||
|
"vasculaire", "cérébral", "cérébrale", "cardiaque",
|
||||||
|
"médecine", "medecine", "réanimation", "reanimation",
|
||||||
|
"pédiatrie", "pediatrie", "gynécologie", "gynecologie",
|
||||||
|
"urologie", "ophtalmologie", "dermatologie", "rhumatologie",
|
||||||
|
"radiologie", "anesthésie", "anesthesie", "consultation",
|
||||||
|
"biologie", "laboratoire", "pharmacie", "kinésithérapie",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Noms d'établissement à préserver si configuré
|
# Noms d'établissement à préserver si configuré
|
||||||
@@ -204,6 +213,8 @@ class Anonymizer:
|
|||||||
count += n
|
count += n
|
||||||
text, n = self._replace_pattern(text, patterns.N_IPP_PATTERN, "ipp")
|
text, n = self._replace_pattern(text, patterns.N_IPP_PATTERN, "ipp")
|
||||||
count += n
|
count += n
|
||||||
|
text, n = self._replace_pattern(text, patterns.N_CSULT_PATTERN, "episode")
|
||||||
|
count += n
|
||||||
text, n = self._replace_pattern(
|
text, n = self._replace_pattern(
|
||||||
text, patterns.CONSULT_ADRESSE_PATTERN, "adresse",
|
text, patterns.CONSULT_ADRESSE_PATTERN, "adresse",
|
||||||
skip_establishment_check=True,
|
skip_establishment_check=True,
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ FRENCH_STOP_WORDS = {
|
|||||||
"peu", "très", "trop", "tout", "tous", "rien", "fait",
|
"peu", "très", "trop", "tout", "tous", "rien", "fait",
|
||||||
"été", "sont", "ont", "qui", "que", "dont", "peut",
|
"été", "sont", "ont", "qui", "que", "dont", "peut",
|
||||||
"cette", "être", "avoir", "faire", "dire", "aussi",
|
"cette", "être", "avoir", "faire", "dire", "aussi",
|
||||||
|
# Mots courts (4-5 chars) trop courants comme sous-parties
|
||||||
|
"fort", "mars", "long", "beau", "noir", "gros",
|
||||||
|
"aide", "note", "date", "heure", "type", "code",
|
||||||
|
"état", "etat", "mise", "prise", "point", "place",
|
||||||
|
"suite", "avant", "après", "apres", "autre",
|
||||||
|
"comme", "entre", "même", "meme", "seul",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -77,10 +77,11 @@ ADDRESS_BLOCK_PATTERN = regex.compile(
|
|||||||
|
|
||||||
# --- Dates de naissance ---
|
# --- Dates de naissance ---
|
||||||
|
|
||||||
# Toutes les variantes : "né(e) le", "née le", "né le", "Né(e) le", "Date de naissance:"
|
# Toutes les variantes : "né(e) le", "née le", "né le", "Né(e) le", "Né(e) le :",
|
||||||
|
# "Date de naissance:", "N° Csult" (consultation anesthésie)
|
||||||
# Accepte les séparateurs / et - (DD/MM/YYYY ou DD-MM-YYYY)
|
# Accepte les séparateurs / et - (DD/MM/YYYY ou DD-MM-YYYY)
|
||||||
DATE_NAISSANCE_PATTERN = regex.compile(
|
DATE_NAISSANCE_PATTERN = regex.compile(
|
||||||
r"(?:[Nn][ée]+(?:\(e\))?\s+le\s+|Date de naissance\s*[:=]?\s*)(\d{2}[/\-]\d{2}[/\-]\d{4})"
|
r"(?:[Nn][ée]+(?:\(e\))?\s+le\s*:?\s*|Date de naissance\s*[:=]?\s*)(\d{2}[/\-]\d{2}[/\-]\d{4})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Noms structurés ---
|
# --- Noms structurés ---
|
||||||
@@ -102,8 +103,9 @@ PATIENT_NAME_PATTERN = regex.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# "MME/Mme/M./MR/Madame/Monsieur" suivi du nom
|
# "MME/Mme/M./MR/Madame/Monsieur" suivi du nom
|
||||||
|
# Chaque mot du nom doit commencer par une majuscule (évite de capturer des phrases)
|
||||||
CIVILITE_NAME_PATTERN = regex.compile(
|
CIVILITE_NAME_PATTERN = regex.compile(
|
||||||
r"(?:MME|Mme|Madame|M\.|Mr|MR|Monsieur)\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\s\.\-]+?)(?:\s+[Nn]é|\s+Date|\n|,)"
|
r"(?:MME|Mme|Madame|M\.|Mr|MR|Monsieur)\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\.\-]+(?:\s+[A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\.\-]+){0,3})(?:\s+[Nn]é|\s+Date|\n|,)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# "DR." / "Dr" / "Docteur" suivi du nom du médecin
|
# "DR." / "Dr" / "Docteur" suivi du nom du médecin
|
||||||
@@ -113,7 +115,7 @@ CIVILITE_NAME_PATTERN = regex.compile(
|
|||||||
# Negative lookahead empêche de capturer "Dr" comme partie du nom
|
# Negative lookahead empêche de capturer "Dr" comme partie du nom
|
||||||
# (cas multi-docteurs sur même ligne : "Dr LEYSSENE David Dr BENARD Yohan").
|
# (cas multi-docteurs sur même ligne : "Dr LEYSSENE David Dr BENARD Yohan").
|
||||||
DR_NAME_PATTERN = regex.compile(
|
DR_NAME_PATTERN = regex.compile(
|
||||||
r"(?:DR\.?|Dr\.?|Docteur)\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\.\-']+(?:[ \t\-'](?!DR\.?\s|Dr\.?\s|Docteur\s)[A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-Za-zéèêëàâäùûüôöîïç\.\-']+){0,2})"
|
r"(?:DR\.?|Dr\.?|Docteur)\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\.\-']+(?:[ \t\-'](?!DR\.?\s|Dr\.?\s|Docteur\s)[A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-Za-zéèêëàâäùûüôöîïç\.\-']+){0,1})"
|
||||||
)
|
)
|
||||||
|
|
||||||
# "Rédigé par" en pied de page CRH
|
# "Rédigé par" en pied de page CRH
|
||||||
@@ -122,8 +124,9 @@ REDIGE_PAR_PATTERN = regex.compile(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# "Liste des destinataires:" suivi de noms
|
# "Liste des destinataires:" suivi de noms
|
||||||
|
# Chaque mot doit commencer par une majuscule (nom propre)
|
||||||
DESTINATAIRE_PATTERN = regex.compile(
|
DESTINATAIRE_PATTERN = regex.compile(
|
||||||
r"(?:Madame|Monsieur|DR\.?|Dr\.?)\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\s\.\-]+?)(?:\n|$)"
|
r"(?:Madame|Monsieur|DR\.?|Dr\.?)\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\.\-]+(?:\s+[A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\.\-]+){0,3})(?:\n|$)"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Noms d'auteurs dans Trackare : "Note d'évolution Prénom NOM DD/MM/YYYY"
|
# Noms d'auteurs dans Trackare : "Note d'évolution Prénom NOM DD/MM/YYYY"
|
||||||
@@ -205,6 +208,11 @@ N_IPP_PATTERN = regex.compile(
|
|||||||
r"N\s+Ipp\s*:\s*(\d{6,10})"
|
r"N\s+Ipp\s*:\s*(\d{6,10})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# "N° Csult : 23117170" ou "N°Csult :23117170" (numéro de consultation)
|
||||||
|
N_CSULT_PATTERN = regex.compile(
|
||||||
|
r"N°?\s*Csult\s*:?\s*(\d{6,10})"
|
||||||
|
)
|
||||||
|
|
||||||
# "Adresse : 15 rue des Lilas 64100 BAYONNE" (consultation anesthésie)
|
# "Adresse : 15 rue des Lilas 64100 BAYONNE" (consultation anesthésie)
|
||||||
CONSULT_ADRESSE_PATTERN = regex.compile(
|
CONSULT_ADRESSE_PATTERN = regex.compile(
|
||||||
r"Adresse\s*:\s*(.+?)(?:\n|$)"
|
r"Adresse\s*:\s*(.+?)(?:\n|$)"
|
||||||
@@ -223,9 +231,13 @@ PERSONNE_PREVENIR_PATTERN = regex.compile(
|
|||||||
r"Personne\s+[àa]\s+pr[ée]venir\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-Za-zéèêëàâäùûüôöîïç\s\-]+?)(?:\s+\d{2}[\s.]|\s*$|\s*\n)",
|
r"Personne\s+[àa]\s+pr[ée]venir\s+([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ][A-Za-zéèêëàâäùûüôöîïç\s\-]+?)(?:\s+\d{2}[\s.]|\s*$|\s*\n)",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Contacts : "Concubine/Conjoint/Époux/Épouse NOM Prénom"
|
# Contacts : "Concubine/Conjoint/Époux/Neveu/Oncle... NOM Prénom"
|
||||||
CONTACT_RELATION_PATTERN = regex.compile(
|
CONTACT_RELATION_PATTERN = regex.compile(
|
||||||
r"(?:Concubin[e]?|Conjoint[e]?|[ÉE]poux|[ÉE]pouse|Compagnon|Compagne|Fils|Fille|Père|Mère|Frère|Sœur|Soeur)\s+"
|
r"(?:Concubin[e]?|Conjoint[e]?|[ÉE]poux|[ÉE]pouse|Compagnon|Compagne"
|
||||||
|
r"|Fils|Fille|Père|Mère|Frère|Sœur|Soeur"
|
||||||
|
r"|Neveu|Nièce|Niece|Oncle|Tante|Cousin[e]?"
|
||||||
|
r"|Beau[\s\-]?père|Belle[\s\-]?mère|Beau[\s\-]?frère|Belle[\s\-]?sœur|Belle[\s\-]?soeur"
|
||||||
|
r"|Ami[e]?|Voisin[e]?|Tuteur|Tutrice|Curateur|Curatrice)\s+"
|
||||||
r"([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ]{2,}(?:\s+[A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\-]+)*)",
|
r"([A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇ]{2,}(?:\s+[A-ZÉÈÊËÀÂÄÙÛÜÔÖÎÏÇa-zéèêëàâäùûüôöîïç\-]+)*)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user