feat: cache Ollama + parallélisation ThreadPool + filtrage DAS renforcé + modules GHM/CPAM/export RUM

- Cache persistant JSON thread-safe pour les résultats Ollama (invalidation par modèle)
- Parallélisation des appels Ollama (ThreadPoolExecutor, 2 workers)
- 6 nouvelles règles de filtrage DAS parasites (doublons, ponctuation, OCR, labo, fragments)
- Client Ollama centralisé (mode JSON natif + retry)
- Module GHM (estimation CMD/sévérité)
- Module contrôle CPAM (parser + contre-argumentation RAG)
- Export RUM (format RSS)
- Viewer enrichi (détail dossier)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-12 13:44:34 +01:00
parent a00e5f1147
commit a58398f5d4
25 changed files with 2872 additions and 97 deletions

View File

@@ -9,7 +9,7 @@ from typing import Optional
logger = logging.getLogger(__name__)
from .cim10_dict import lookup as dict_lookup, normalize_text
from .cim10_dict import lookup as dict_lookup, normalize_text, normalize_code, validate_code as cim10_validate
from .ccam_dict import lookup as ccam_lookup, validate_code as ccam_validate
from .das_filter import clean_diagnostic_text, is_valid_diagnostic_text
from ..config import (
@@ -118,6 +118,9 @@ def extract_medical_info(
# Post-processing : validation des codes CCAM contre le dictionnaire
_validate_ccam(dossier)
# Post-processing : validation des codes CIM-10 contre le dictionnaire
_validate_cim10(dossier)
# Post-processing : exclusions symptôme vs diagnostic précis
_apply_exclusion_rules(dossier)
@@ -663,6 +666,68 @@ def _validate_ccam(dossier: DossierMedical) -> None:
)
_INVALID_CODE_PATTERNS = {"aucun", "none", "n/a", "non_codable", "aucun_code_valide", "inconnu"}
def _fallback_cim10(texte: str) -> str | None:
"""Tente de trouver un code CIM-10 via le dictionnaire à partir du texte diagnostic."""
code = dict_lookup(texte, domain_overrides=CIM10_MAP)
if code:
is_valid, _ = cim10_validate(code)
if is_valid:
return code
return None
def _validate_cim10(dossier: DossierMedical) -> None:
"""Valide les codes CIM-10 suggérés par Ollama contre le dictionnaire."""
diags: list[tuple[str, Diagnostic]] = []
if dossier.diagnostic_principal:
diags.append(("DP", dossier.diagnostic_principal))
for das in dossier.diagnostics_associes:
diags.append(("DAS", das))
for type_diag, diag in diags:
if not diag.cim10_suggestion:
continue
# Rejeter les hallucinations
if diag.cim10_suggestion.lower().strip() in _INVALID_CODE_PATTERNS:
fallback = _fallback_cim10(diag.texte)
if fallback:
dossier.alertes_codage.append(
f"CIM-10 {type_diag} ({diag.texte}) : code rejeté « {diag.cim10_suggestion} » → fallback {fallback}"
)
diag.cim10_suggestion = fallback
diag.cim10_confidence = "medium"
else:
dossier.alertes_codage.append(
f"CIM-10 {type_diag} ({diag.texte}) : code rejeté « {diag.cim10_suggestion} »"
)
diag.cim10_suggestion = None
diag.cim10_confidence = None
continue
# Normaliser le format (K810 → K81.0)
diag.cim10_suggestion = normalize_code(diag.cim10_suggestion)
# Valider contre le dictionnaire
is_valid, label = cim10_validate(diag.cim10_suggestion)
if not is_valid:
fallback = _fallback_cim10(diag.texte)
if fallback:
dossier.alertes_codage.append(
f"CIM-10 {type_diag} {diag.cim10_suggestion} ({diag.texte}) : code invalide → fallback {fallback}"
)
diag.cim10_suggestion = fallback
diag.cim10_confidence = "medium"
else:
dossier.alertes_codage.append(
f"CIM-10 {type_diag} {diag.cim10_suggestion} ({diag.texte}) : code absent du dictionnaire CIM-10"
)
diag.cim10_confidence = "low"
def _find_act_date(text: str, act_pattern: str) -> str | None:
"""Trouve la date associée à un acte."""
# Chercher "acte le DD/MM" ou "acte le DD/MM/YYYY"
@@ -705,7 +770,7 @@ def _apply_severity_rules(dossier: DossierMedical) -> None:
"""Enrichit les diagnostics avec les informations de sévérité heuristique."""
try:
from .severity import enrich_dossier_severity
alertes = enrich_dossier_severity(
alertes, _cma_count, _cms_count = enrich_dossier_severity(
dossier.diagnostic_principal, dossier.diagnostics_associes,
)
dossier.alertes_codage.extend(alertes)