feat: dashboard métriques + vue CPAM agrégée dans le viewer

Ajout d'un dashboard global (distribution confiance DP, top 15 codes CIM-10,
types GHM, sévérité) et d'une page listant tous les contrôles CPAM agrégés.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-02-13 18:11:21 +01:00
parent 906a2797e5
commit ee661dae1d
4 changed files with 353 additions and 0 deletions

View File

@@ -13,6 +13,8 @@ from markupsafe import Markup
from werkzeug.utils import secure_filename
from collections import Counter
from ..config import STRUCTURED_DIR, OLLAMA_URL, CCAM_DICT_PATH, DossierMedical, ALLOWED_EXTENSIONS, UPLOAD_MAX_SIZE_MB
from .. import config as cfg
from .referentiels import ReferentielManager
@@ -54,6 +56,104 @@ def compute_group_stats(items: list[dict]) -> dict:
}
def compute_dashboard_stats(groups: dict[str, list[dict]]) -> dict:
"""Calcule les statistiques globales du pipeline pour le dashboard."""
total_dossiers = len(groups)
total_fichiers = 0
total_das = 0
total_actes = 0
total_alertes = 0
total_cma = 0
total_cpam = 0
dp_confidence: Counter = Counter()
dp_validity: Counter = Counter()
code_counter: Counter = Counter()
ghm_types: Counter = Counter()
severity_dist: Counter = Counter()
processing_times: list[float] = []
for items in groups.values():
total_fichiers += len(items)
for item in items:
d = item["dossier"]
total_das += len(d.diagnostics_associes)
total_actes += len(d.actes_ccam)
total_alertes += len(d.alertes_codage)
total_cpam += len(d.controles_cpam)
if d.processing_time_s is not None:
processing_times.append(d.processing_time_s)
# DP confidence & validity
dp = d.diagnostic_principal
if dp:
conf = dp.cim10_confidence or "none"
dp_confidence[conf] += 1
if dp.cim10_suggestion:
dp_validity["valide"] += 1
code_counter[dp.cim10_suggestion] += 1
else:
dp_validity["absent"] += 1
else:
dp_confidence["none"] += 1
dp_validity["absent"] += 1
# DAS codes + CMA
for das in d.diagnostics_associes:
if das.cim10_suggestion:
code_counter[das.cim10_suggestion] += 1
if das.est_cma:
total_cma += 1
if dp and dp.est_cma:
total_cma += 1
# GHM
ghm = d.ghm_estimation
if ghm:
if ghm.type_ghm:
ghm_types[ghm.type_ghm] += 1
severity_dist[ghm.severite] += 1
top_codes = code_counter.most_common(15)
top_max = top_codes[0][1] if top_codes else 1
return {
"total_dossiers": total_dossiers,
"total_fichiers": total_fichiers,
"total_das": total_das,
"total_actes": total_actes,
"total_alertes": total_alertes,
"total_cma": total_cma,
"total_cpam": total_cpam,
"dp_confidence": dict(dp_confidence),
"dp_validity": dict(dp_validity),
"top_codes": top_codes,
"top_max": top_max,
"ghm_types": dict(ghm_types),
"severity_dist": dict(severity_dist),
"processing_time_total": sum(processing_times),
"processing_time_avg": sum(processing_times) / len(processing_times) if processing_times else 0,
}
def collect_cpam_controls(groups: dict[str, list[dict]]) -> list[dict]:
"""Collecte tous les contrôles CPAM de tous les dossiers."""
controls = []
for group_name, items in groups.items():
for item in items:
d = item["dossier"]
dp_code = d.diagnostic_principal.cim10_suggestion if d.diagnostic_principal else None
for ctrl in d.controles_cpam:
controls.append({
"group_name": group_name,
"filepath": item["path_rel"],
"ctrl": ctrl,
"dp_code": dp_code,
})
controls.sort(key=lambda c: c["ctrl"].numero_ogc)
return controls
def load_ccam_dict() -> dict[str, dict]:
"""Charge le dictionnaire CCAM pour les regroupements."""
if CCAM_DICT_PATH.exists():
@@ -255,6 +355,18 @@ def create_app() -> Flask:
current_group=current_group,
)
@app.route("/dashboard")
def dashboard():
groups = scan_dossiers()
stats = compute_dashboard_stats(groups)
return render_template("dashboard.html", stats=stats, groups=groups)
@app.route("/cpam")
def cpam_list():
groups = scan_dossiers()
controls = collect_cpam_controls(groups)
return render_template("cpam.html", controls=controls, total=len(controls), groups=groups)
@app.route("/admin/models", methods=["GET"])
def list_models():
models = fetch_ollama_models()