fix: filtre bruit Trackare — antécédents parasites + répétitions DAS

- das_filter: regex anti-répétition gère les espaces entre mots concaténés
  ("VentilationVentilation Ventilation..." désormais rejeté)
- cim10_extractor: regex antécédents s'arrête à "Signes Vitaux" (ne capture
  plus le tableau de surveillance)
- Nouveau _is_valid_antecedent() filtre noms de service, mots de surveillance
  isolés, infos admin (RPPS), répétitions, Mode de vie
- 28 nouveaux tests (TestIsValidAntecedent + das_filter repetition)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-18 19:20:50 +01:00
parent f7d87f2602
commit fe22c0f0f5
4 changed files with 145 additions and 18 deletions

View File

@@ -528,10 +528,57 @@ def _extract_actes(text: str, dossier: DossierMedical) -> None:
acte.code_ccam_suggestion = code
_ANTECEDENT_NOISE = (
"item de", "surveillance", "température", "signes vitaux",
"pouls", "type de note", "aucune donnée", "renseignée",
"habitudes de vie", "systolique", "diastolique", "saturation",
"texte libre", "mode de vie", "n° rpps",
)
_SURVEILLANCE_SINGLE_WORDS = frozenset({
"moyenne", "ventilation", "echelle", "gauche", "droite",
"capillaire", "repos", "diurèse", "glycémie", "ambiant",
})
def _is_valid_antecedent(line: str) -> bool:
"""Filtre les lignes parasites du bloc antécédents (bruit Trackare)."""
if not line or len(line) <= 5 or line == "0":
return False
if re.match(r"^\d", line):
return False
low = line.lower()
# Mots-clés de bruit (sous-chaînes)
if any(kw in low for kw in _ANTECEDENT_NOISE):
return False
words = low.split()
# Mots isolés de tableau de surveillance
if len(words) == 1 and low in _SURVEILLANCE_SINGLE_WORDS:
return False
# Noms de service (tout majuscules, court)
if line.isupper() and len(line) < 40:
return False
# Mots concaténés ou répétés avec espaces : "VentilationVentilation Ventilation..."
if re.match(r'^([a-zà-ÿ]{3,})(\s*\1)+\s*$', line, re.IGNORECASE):
return False
# Mots répétés mélangés (≥ 3 occurrences du même mot)
if len(words) >= 3:
from collections import Counter
if Counter(words).most_common(1)[0][1] >= 3:
return False
# Deux mots identiques
if len(words) == 2 and len(set(words)) == 1:
return False
# Identifiants administratifs isolés
if re.match(r'^\[MEDECIN\]\s', line) and len(line) < 30:
return False
return True
def _extract_antecedents(text: str, dossier: DossierMedical) -> None:
"""Extrait les antécédents."""
m = re.search(
r"Antécédents?\s*[:]?\s*\n?(.*?)(?=\n\s*(?:Traitements?\s*[:]|Allergie|Histoire de la maladie|Examen clinique|\n\n))",
r"Antécédents?\s*[:]?\s*\n?(.*?)(?=\n\s*(?:Traitements?\s*[:]|Allergie|Histoire de la maladie|Examen clinique|Signes\s+[Vv]itaux|Observations?\s+m[eé]dicale|Passage aux|\n\n))",
text,
re.DOTALL | re.IGNORECASE,
)
@@ -539,21 +586,7 @@ def _extract_antecedents(text: str, dossier: DossierMedical) -> None:
block = m.group(1).strip()
for line in block.split("\n"):
line = line.strip().lstrip("- •")
# Filtrer les lignes non pertinentes
if (line and len(line) > 5 and line != "0"
and not re.match(r"^\d", line)
and "Item de" not in line
and "surveillance" not in line.lower()
and "Température" not in line
and "Signes Vitaux" not in line
and "Pouls" not in line
and "Type de note" not in line
and "Aucune donnée" not in line
and "renseignée" not in line
and "habitudes de vie" not in line
and "Systolique" not in line
and "Diastolique" not in line
and "Saturation" not in line):
if _is_valid_antecedent(line):
dossier.antecedents.append(line)

View File

@@ -39,8 +39,8 @@ def is_valid_diagnostic_text(text: str) -> bool:
if re.match(r"^[A-ZÀ-Ú]\s*\d{1,3}([.,]\d+)?$", t):
return False
# 4. Mots concaténés : "Ventilationventilation"
if re.match(r"^([a-zà-ÿ]{3,})\1+[a-zà-ÿ]*$", t, re.IGNORECASE):
# 4. Mots concaténés et/ou répétés avec espaces : "VentilationVentilation Ventilation..."
if re.match(r"^([a-zà-ÿ]{3,})(\s*\1)+\s*$", t, re.IGNORECASE):
return False
# 5. Mots répétés : tous identiques ("Absence absence", "Anticoagulant anticoagulant")