"""Détection des incompatibilités de non-cumul entre actes CCAM. Implémente 3 règles heuristiques basées sur les principes T2A : 1. Même code de base (7 caractères) avec activités différentes 2. Même regroupement chirurgical le même jour 3. Paires de regroupements incompatibles connues """ from __future__ import annotations import logging from typing import TYPE_CHECKING if TYPE_CHECKING: from ..config import ActeCCAM logger = logging.getLogger(__name__) # Regroupements chirurgicaux soumis à cumul restreint (un seul par jour) REGROUPEMENT_UNIQUE_PAR_JOUR: set[str] = { "ADC", # Actes de chirurgie "ACO", # Actes de chirurgie orthopédique "ADO", # Actes de chirurgie ORL "ADA", # Actes de chirurgie abdominale/digestive "ADE", # Actes de chirurgie endoscopique } # Paires de regroupements incompatibles NONCUMUL_REGROUPEMENT_PAIRS: set[frozenset[str]] = { frozenset({"ADC", "ADE"}), frozenset({"ADC", "ADO"}), frozenset({"ACO", "ADE"}), } def _get_regroupement(acte: ActeCCAM) -> str | None: """Récupère le regroupement d'un acte depuis le dictionnaire CCAM.""" if not acte.code_ccam_suggestion: return None try: from .ccam_dict import load_dict d = load_dict() info = d.get(acte.code_ccam_suggestion) if info and isinstance(info, dict): return info.get("regroupement") except Exception: pass return None def check_noncumul(actes: list[ActeCCAM]) -> list[str]: """Vérifie les règles de non-cumul entre actes CCAM. Args: actes: Liste d'actes CCAM d'un dossier médical. Returns: Liste d'alertes de non-cumul détectées. """ if len(actes) < 2: return [] alertes: list[str] = [] # Enrichir les actes avec leur regroupement actes_info: list[tuple[ActeCCAM, str | None]] = [ (acte, _get_regroupement(acte)) for acte in actes ] # Règle 1 : même code de base (7 premiers caractères), activités différentes codes_base: dict[str, list[ActeCCAM]] = {} for acte in actes: code = acte.code_ccam_suggestion if code and len(code) >= 7: base = code[:7] codes_base.setdefault(base, []).append(acte) for base, group in codes_base.items(): if len(group) > 1: codes_full = [a.code_ccam_suggestion for a in group] alertes.append( f"NON-CUMUL: codes de même base {base} avec variantes " f"({', '.join(codes_full)}) — vérifier la facturation" ) # Règle 2 : même regroupement chirurgical le même jour regroup_par_jour: dict[tuple[str, str | None], list[ActeCCAM]] = {} for acte, regroup in actes_info: if regroup and regroup in REGROUPEMENT_UNIQUE_PAR_JOUR: key = (regroup, acte.date) regroup_par_jour.setdefault(key, []).append(acte) for (regroup, date), group in regroup_par_jour.items(): if len(group) > 1: codes = [a.code_ccam_suggestion or "?" for a in group] jour = f" le {date}" if date else "" alertes.append( f"NON-CUMUL: {len(group)} actes du regroupement {regroup}{jour} " f"({', '.join(codes)}) — cumul restreint" ) # Règle 3 : paires de regroupements incompatibles regroups_seen: list[tuple[str, ActeCCAM]] = [ (r, a) for a, r in actes_info if r ] checked: set[frozenset[int]] = set() for i, (r1, a1) in enumerate(regroups_seen): for j, (r2, a2) in enumerate(regroups_seen): if i >= j: continue pair_key = frozenset({i, j}) if pair_key in checked: continue checked.add(pair_key) pair = frozenset({r1, r2}) if pair in NONCUMUL_REGROUPEMENT_PAIRS: alertes.append( f"NON-CUMUL: regroupements incompatibles {r1}/{r2} " f"({a1.code_ccam_suggestion or '?'} + {a2.code_ccam_suggestion or '?'})" ) return alertes