diff --git a/src/anonymization/anonymizer.py b/src/anonymization/anonymizer.py index 69f1b23..e0691ee 100644 --- a/src/anonymization/anonymizer.py +++ b/src/anonymization/anonymizer.py @@ -64,6 +64,15 @@ MEDICAL_TERMS_WHITELIST = { "coordonnateur", "fédération", "federation", "institut", "cancérologie", "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é @@ -204,6 +213,8 @@ class Anonymizer: count += n text, n = self._replace_pattern(text, patterns.N_IPP_PATTERN, "ipp") count += n + text, n = self._replace_pattern(text, patterns.N_CSULT_PATTERN, "episode") + count += n text, n = self._replace_pattern( text, patterns.CONSULT_ADRESSE_PATTERN, "adresse", skip_establishment_check=True, diff --git a/src/anonymization/entity_registry.py b/src/anonymization/entity_registry.py index 0913dc6..e4ca6ba 100644 --- a/src/anonymization/entity_registry.py +++ b/src/anonymization/entity_registry.py @@ -16,6 +16,12 @@ FRENCH_STOP_WORDS = { "peu", "très", "trop", "tout", "tous", "rien", "fait", "été", "sont", "ont", "qui", "que", "dont", "peut", "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", } diff --git a/src/anonymization/regex_patterns.py b/src/anonymization/regex_patterns.py index 618cce3..02e3879 100644 --- a/src/anonymization/regex_patterns.py +++ b/src/anonymization/regex_patterns.py @@ -77,10 +77,11 @@ ADDRESS_BLOCK_PATTERN = regex.compile( # --- 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) 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 --- @@ -102,8 +103,9 @@ PATIENT_NAME_PATTERN = regex.compile( ) # "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( - 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 @@ -113,7 +115,7 @@ CIVILITE_NAME_PATTERN = regex.compile( # Negative lookahead empêche de capturer "Dr" comme partie du nom # (cas multi-docteurs sur même ligne : "Dr LEYSSENE David Dr BENARD Yohan"). 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 @@ -122,8 +124,9 @@ REDIGE_PAR_PATTERN = regex.compile( ) # "Liste des destinataires:" suivi de noms +# Chaque mot doit commencer par une majuscule (nom propre) 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" @@ -205,6 +208,11 @@ N_IPP_PATTERN = regex.compile( 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) CONSULT_ADRESSE_PATTERN = regex.compile( 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)", ) -# Contacts : "Concubine/Conjoint/Époux/Épouse NOM Prénom" +# Contacts : "Concubine/Conjoint/Époux/Neveu/Oncle... NOM Prénom" 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éèêëàâäùûüôöîïç\-]+)*)", )