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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user