feat: méthode TIM experte CPAM + moteur de règles étendu
CPAM — Méthode TIM (mémoire en défense) : - Réécriture CPAM_ARGUMENTATION avec raisonnement 5 passes TIM (contexte admin → motif réel → confrontation bio → hiérarchie → validation défensive) - _BIO_THRESHOLDS (19 entrées) + _build_bio_confrontation() pour confrontation biologie/diagnostic avec seuils chiffrés et verdicts - _format_response() dual format : nouveau TIM (moyens numérotés, tableau bio, codes non défendables, conclusion dispositive) + rétrocompat legacy - CPAM_ADVERSARIAL mis à jour pour vérifier honnêteté intellectuelle - Tests adaptés + 12 nouveaux tests (bio confrontation, format TIM) Moteur de règles : - Nouvelles règles YAML : demographic, diagnostic_conflicts, procedure_diagnosis, temporal, parcours - Bio extraction FAISS (synonymes vectoriels) - Veto engine enrichi (citations, Trackare skip, règles démographiques) - Decision engine : _apply_bio_rules_gen() + matchers analytiques Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
125
src/config.py
125
src/config.py
@@ -28,6 +28,11 @@ CONFIG_DIR = BASE_DIR / "config"
|
||||
REFERENCE_RANGES_PATH = CONFIG_DIR / "reference_ranges.yaml"
|
||||
BIO_RULES_PATH = CONFIG_DIR / "bio_rules.yaml"
|
||||
LAB_SANITY_PATH = CONFIG_DIR / "lab_value_sanity.yaml"
|
||||
DEMOGRAPHIC_RULES_PATH = CONFIG_DIR / "demographic_rules.yaml"
|
||||
DIAGNOSTIC_CONFLICTS_PATH = CONFIG_DIR / "diagnostic_conflicts.yaml"
|
||||
PROCEDURE_DIAGNOSIS_RULES_PATH = CONFIG_DIR / "procedure_diagnosis_rules.yaml"
|
||||
TEMPORAL_RULES_PATH = CONFIG_DIR / "temporal_rules.yaml"
|
||||
PARCOURS_RULES_PATH = CONFIG_DIR / "parcours_rules.yaml"
|
||||
RULES_DIR = CONFIG_DIR / "rules"
|
||||
RULES_BASE_PATH = RULES_DIR / "base.yaml"
|
||||
RULES_ENABLED_PATH = RULES_DIR / "enabled.yaml"
|
||||
@@ -128,6 +133,7 @@ UPLOAD_MAX_SIZE_MB = 50
|
||||
ALLOWED_EXTENSIONS = {".pdf", ".csv", ".xlsx", ".xls", ".txt"}
|
||||
CIM10_DICT_PATH = BASE_DIR / "data" / "cim10_dict.json"
|
||||
CIM10_SUPPLEMENTS_PATH = BASE_DIR / "data" / "cim10_supplements.json"
|
||||
BIO_CONCEPTS_PATH = BASE_DIR / "data" / "bio_concepts.json"
|
||||
CMA_LEVELS_PATH = BASE_DIR / "data" / "cma_levels.json"
|
||||
CCAM_DICT_PATH = BASE_DIR / "data" / "ccam_dict.json"
|
||||
CIM10_PDF = Path(os.environ.get("T2A_CIM10_PDF", "/home/dom/ai/aivanov_CIM/cim-10-fr_2026_a_usage_pmsi_version_provisoire_111225.pdf"))
|
||||
@@ -247,6 +253,124 @@ def load_bio_rules() -> Dict[str, Any]:
|
||||
return defaults
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_demographic_rules() -> Dict[str, Any]:
|
||||
"""Charge les règles démographiques (sexe/âge) depuis config/demographic_rules.yaml."""
|
||||
defaults: Dict[str, Any] = {
|
||||
"version": 1,
|
||||
"sex_rules": {},
|
||||
"age_rules": {},
|
||||
}
|
||||
path = DEMOGRAPHIC_RULES_PATH
|
||||
if not path.exists():
|
||||
return defaults
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(data, dict):
|
||||
return defaults
|
||||
merged = dict(defaults)
|
||||
for k, v in data.items():
|
||||
merged[k] = v
|
||||
return merged
|
||||
except Exception:
|
||||
return defaults
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_diagnostic_conflicts() -> Dict[str, Any]:
|
||||
"""Charge les conflits diagnostics depuis config/diagnostic_conflicts.yaml."""
|
||||
defaults: Dict[str, Any] = {
|
||||
"version": 1,
|
||||
"mutual_exclusions": [],
|
||||
"incompatibilities": [],
|
||||
}
|
||||
path = DIAGNOSTIC_CONFLICTS_PATH
|
||||
if not path.exists():
|
||||
return defaults
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(data, dict):
|
||||
return defaults
|
||||
merged = dict(defaults)
|
||||
for k, v in data.items():
|
||||
merged[k] = v
|
||||
return merged
|
||||
except Exception:
|
||||
return defaults
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_procedure_diagnosis_rules() -> Dict[str, Any]:
|
||||
"""Charge les règles de corrélation actes/diagnostics depuis config/procedure_diagnosis_rules.yaml."""
|
||||
defaults: Dict[str, Any] = {
|
||||
"version": 1,
|
||||
"rules": [],
|
||||
}
|
||||
path = PROCEDURE_DIAGNOSIS_RULES_PATH
|
||||
if not path.exists():
|
||||
return defaults
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(data, dict):
|
||||
return defaults
|
||||
merged = dict(defaults)
|
||||
for k, v in data.items():
|
||||
merged[k] = v
|
||||
return merged
|
||||
except Exception:
|
||||
return defaults
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_temporal_rules() -> Dict[str, Any]:
|
||||
"""Charge les règles temporelles depuis config/temporal_rules.yaml."""
|
||||
defaults: Dict[str, Any] = {
|
||||
"version": 1,
|
||||
"rules": [],
|
||||
}
|
||||
path = TEMPORAL_RULES_PATH
|
||||
if not path.exists():
|
||||
return defaults
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(data, dict):
|
||||
return defaults
|
||||
merged = dict(defaults)
|
||||
for k, v in data.items():
|
||||
merged[k] = v
|
||||
return merged
|
||||
except Exception:
|
||||
return defaults
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_parcours_rules() -> Dict[str, Any]:
|
||||
"""Charge les règles de parcours patient depuis config/parcours_rules.yaml."""
|
||||
defaults: Dict[str, Any] = {
|
||||
"version": 1,
|
||||
"documentary_rules": {},
|
||||
"pathway_rules": {},
|
||||
}
|
||||
path = PARCOURS_RULES_PATH
|
||||
if not path.exists():
|
||||
return defaults
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
||||
if not isinstance(data, dict):
|
||||
return defaults
|
||||
merged = dict(defaults)
|
||||
for k, v in data.items():
|
||||
merged[k] = v
|
||||
return merged
|
||||
except Exception:
|
||||
return defaults
|
||||
|
||||
|
||||
# --- Garde-fous de parsing des valeurs biologiques (anti-OCR) ---
|
||||
|
||||
|
||||
@@ -827,6 +951,7 @@ class VetoIssue(BaseModel):
|
||||
severity: str # HARD | MEDIUM | LOW
|
||||
where: str
|
||||
message: str
|
||||
citation: Optional[str] = None
|
||||
|
||||
|
||||
class VetoReport(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user