feat: traçabilité source systématique + viewer interactif

Ajoute source_page/source_excerpt à tous les types (biologie, imagerie,
traitements, actes CCAM, antécédents, complications). Convertit antecedents
et complications en types structurés (Antecedent/Complication) avec
validators backward-compat pour les vieux JSON. Étend _apply_source_tracking
à tous les éléments du dossier. Ajoute un endpoint /api/source-text/ et un
modal interactif dans le viewer avec surlignage du texte source.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-18 20:59:50 +01:00
parent fe22c0f0f5
commit 40934fdc39
10 changed files with 500 additions and 47 deletions

View File

@@ -7,7 +7,7 @@ from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, field_validator
load_dotenv()
@@ -125,24 +125,44 @@ class ActeCCAM(BaseModel):
date: Optional[str] = None
validite: Optional[str] = None # "valide" | "obsolete" | "non_verifie"
alertes: list[str] = Field(default_factory=list)
source_page: Optional[int] = None
source_excerpt: Optional[str] = None
class Traitement(BaseModel):
medicament: str
posologie: Optional[str] = None
code_atc: Optional[str] = None
source_page: Optional[int] = None
source_excerpt: Optional[str] = None
class BiologieCle(BaseModel):
test: str
valeur: Optional[str] = None
anomalie: Optional[bool] = None
source_page: Optional[int] = None
source_excerpt: Optional[str] = None
class Imagerie(BaseModel):
type: str
conclusion: Optional[str] = None
score: Optional[str] = None
source_page: Optional[int] = None
source_excerpt: Optional[str] = None
class Antecedent(BaseModel):
texte: str
source_page: Optional[int] = None
source_excerpt: Optional[str] = None
class Complication(BaseModel):
texte: str
source_page: Optional[int] = None
source_excerpt: Optional[str] = None
class DossierMedical(BaseModel):
@@ -152,17 +172,45 @@ class DossierMedical(BaseModel):
diagnostic_principal: Optional[Diagnostic] = None
diagnostics_associes: list[Diagnostic] = Field(default_factory=list)
actes_ccam: list[ActeCCAM] = Field(default_factory=list)
antecedents: list[str] = Field(default_factory=list)
antecedents: list[Antecedent] = Field(default_factory=list)
traitements_sortie: list[Traitement] = Field(default_factory=list)
biologie_cle: list[BiologieCle] = Field(default_factory=list)
imagerie: list[Imagerie] = Field(default_factory=list)
complications: list[str] = Field(default_factory=list)
complications: list[Complication] = Field(default_factory=list)
alertes_codage: list[str] = Field(default_factory=list)
source_files: list[str] = Field(default_factory=list)
ghm_estimation: Optional[GHMEstimation] = None
controles_cpam: list[ControleCPAM] = Field(default_factory=list)
processing_time_s: float | None = None
@field_validator("antecedents", mode="before")
@classmethod
def _coerce_antecedents(cls, v):
"""Backward compat : convertit les anciennes list[str] en list[Antecedent]."""
if not isinstance(v, list):
return v
result = []
for item in v:
if isinstance(item, str):
result.append({"texte": item})
else:
result.append(item)
return result
@field_validator("complications", mode="before")
@classmethod
def _coerce_complications(cls, v):
"""Backward compat : convertit les anciennes list[str] en list[Complication]."""
if not isinstance(v, list):
return v
result = []
for item in v:
if isinstance(item, str):
result.append({"texte": item})
else:
result.append(item)
return result
# --- Rapport d'anonymisation ---