Permet aux médecins DIM de valider/corriger les codes CIM-10 extraits par le pipeline pour construire un gold standard (50 dossiers). - ValidationManager : gestion annotations JSON dans data/gold_standard/ - Script sélection 50 dossiers (25 CPAM + 25 stratifiés CMD/confiance) - Routes /validation, /api/cim10/search, /api/validation/save, /validation/metrics - Formulaire avec autocomplete CIM-10, boutons Correct/Modifier/Supprimer - Dashboard métriques : precision, recall, F1, hallucination par confiance/source Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
244 lines
9.3 KiB
HTML
244 lines
9.3 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Métriques Validation DIM{% endblock %}
|
|
|
|
{% block sidebar %}
|
|
{% for group_name, items in groups.items() %}
|
|
<div class="group-title">{{ group_name | format_dossier_name }}</div>
|
|
{% for item in items %}
|
|
{% if 'fusionne' in item.name %}
|
|
<a href="/dossier/{{ item.path_rel }}" class="sidebar-fusionne">★ Fusionné</a>
|
|
{% else %}
|
|
<a href="/dossier/{{ item.path_rel }}">{{ item.name | format_doc_name }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endfor %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1.5rem;">
|
|
<a href="/validation" class="back">Retour à la liste</a>
|
|
<h2 style="margin:0;">Métriques de qualité</h2>
|
|
</div>
|
|
|
|
{% if metrics.total_valides == 0 %}
|
|
<div class="card" style="text-align:center;padding:2rem;">
|
|
<p style="color:#64748b;font-size:0.9rem;">Aucun dossier validé pour le moment.</p>
|
|
<p style="margin-top:0.5rem;font-size:0.85rem;color:#94a3b8;">
|
|
Validez des dossiers depuis la <a href="/validation">liste de validation</a> pour voir les métriques.
|
|
</p>
|
|
</div>
|
|
{% else %}
|
|
|
|
<!-- Progression -->
|
|
<div class="card" style="margin-bottom:1.5rem;">
|
|
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:0.75rem;">
|
|
<span style="font-weight:600;font-size:0.9rem;">Progression</span>
|
|
<span style="font-size:0.85rem;color:#64748b;">{{ metrics.total_valides }} / {{ total_selection }} dossiers validés</span>
|
|
</div>
|
|
<div style="background:#e2e8f0;border-radius:9999px;height:12px;overflow:hidden;">
|
|
{% set pct = (metrics.total_valides / total_selection * 100) if total_selection > 0 else 0 %}
|
|
<div style="width:{{ pct }}%;background:#16a34a;transition:width 0.3s;height:100%;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Métriques DP + DAS côte à côte -->
|
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1.5rem;">
|
|
|
|
<!-- DP -->
|
|
<div class="card">
|
|
<h3>Diagnostic Principal</h3>
|
|
<div class="info-grid" style="margin-top:0.75rem;">
|
|
<div class="info-item">
|
|
<label>Accuracy</label>
|
|
<span style="font-size:1.3rem;font-weight:700;color:#16a34a;">{{ "%.1f" | format(metrics.dp.accuracy * 100) }}%</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Correct</label>
|
|
<span>{{ metrics.dp.correct }} / {{ metrics.dp.total }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Modifié</label>
|
|
<span style="color:#ca8a04;">{{ metrics.dp.modifie }}</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Supprimé</label>
|
|
<span style="color:#dc2626;">{{ metrics.dp.supprime }}</span>
|
|
</div>
|
|
</div>
|
|
<!-- Barre visuelle -->
|
|
<div style="margin-top:1rem;display:flex;height:20px;border-radius:6px;overflow:hidden;">
|
|
{% set dp_t = metrics.dp.total or 1 %}
|
|
<div style="width:{{ metrics.dp.correct / dp_t * 100 }}%;background:#16a34a;" title="Correct"></div>
|
|
<div style="width:{{ metrics.dp.modifie / dp_t * 100 }}%;background:#eab308;" title="Modifié"></div>
|
|
<div style="width:{{ metrics.dp.supprime / dp_t * 100 }}%;background:#dc2626;" title="Supprimé"></div>
|
|
</div>
|
|
<div style="display:flex;gap:1rem;margin-top:0.35rem;font-size:0.7rem;color:#64748b;">
|
|
<span><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#16a34a;margin-right:3px;"></span>Correct</span>
|
|
<span><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#eab308;margin-right:3px;"></span>Modifié</span>
|
|
<span><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:#dc2626;margin-right:3px;"></span>Supprimé</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DAS -->
|
|
<div class="card">
|
|
<h3>Diagnostics Associés</h3>
|
|
<div class="info-grid" style="margin-top:0.75rem;">
|
|
<div class="info-item">
|
|
<label>Precision</label>
|
|
<span style="font-size:1.3rem;font-weight:700;color:#1d4ed8;">{{ "%.1f" | format(metrics.das.precision * 100) }}%</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Recall</label>
|
|
<span style="font-size:1.3rem;font-weight:700;color:#7c3aed;">{{ "%.1f" | format(metrics.das.recall * 100) }}%</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>F1-score</label>
|
|
<span style="font-size:1.3rem;font-weight:700;color:#0f172a;">{{ "%.1f" | format(metrics.das.f1 * 100) }}%</span>
|
|
</div>
|
|
<div class="info-item">
|
|
<label>Hallucination</label>
|
|
<span style="font-size:1.3rem;font-weight:700;color:#dc2626;">{{ "%.1f" | format(metrics.das.hallucination_rate * 100) }}%</span>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:0.75rem;font-size:0.8rem;color:#64748b;">
|
|
<div style="display:flex;gap:1.5rem;">
|
|
<span>Pipeline : {{ metrics.das.total_pipeline }} DAS</span>
|
|
<span>Référence DIM : {{ metrics.das.reference }}</span>
|
|
<span style="color:#16a34a;">Correct : {{ metrics.das.correct }}</span>
|
|
<span style="color:#ca8a04;">Modifié : {{ metrics.das.modifie }}</span>
|
|
<span style="color:#dc2626;">Supprimé : {{ metrics.das.supprime }}</span>
|
|
<span style="color:#7c3aed;">Ajouté : {{ metrics.das.ajoutes }}</span>
|
|
</div>
|
|
</div>
|
|
<!-- Taux manqués -->
|
|
<div style="margin-top:0.5rem;font-size:0.8rem;">
|
|
<span style="color:#64748b;">Taux DAS manqués : </span>
|
|
<span style="font-weight:600;color:#7c3aed;">{{ "%.1f" | format(metrics.das.miss_rate * 100) }}%</span>
|
|
<span style="color:#94a3b8;font-size:0.75rem;"> ({{ metrics.das.ajoutes }} ajoutés / {{ metrics.das.reference }} référence)</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Ventilation par confiance -->
|
|
{% if metrics.by_confidence %}
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<h3>Par niveau de confiance</h3>
|
|
<table style="margin-top:0.75rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>Confiance</th>
|
|
<th>Total DAS</th>
|
|
<th>Correct</th>
|
|
<th>Modifié</th>
|
|
<th>Supprimé</th>
|
|
<th>Precision</th>
|
|
<th>Hallucination</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for conf, bucket in metrics.by_confidence.items() %}
|
|
<tr>
|
|
<td>{{ conf | confidence_badge }}</td>
|
|
<td>{{ bucket.total }}</td>
|
|
<td style="color:#16a34a;">{{ bucket.correct }}</td>
|
|
<td style="color:#ca8a04;">{{ bucket.modifie }}</td>
|
|
<td style="color:#dc2626;">{{ bucket.supprime }}</td>
|
|
<td style="font-weight:600;">{{ "%.1f" | format(bucket.precision * 100) }}%</td>
|
|
<td style="font-weight:600;color:#dc2626;">{{ "%.1f" | format(bucket.hallucination * 100) }}%</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Ventilation par source -->
|
|
{% if metrics.by_source %}
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<h3>Par source d'extraction</h3>
|
|
<table style="margin-top:0.75rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>Source</th>
|
|
<th>Total DAS</th>
|
|
<th>Correct</th>
|
|
<th>Modifié</th>
|
|
<th>Supprimé</th>
|
|
<th>Precision</th>
|
|
<th>Hallucination</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for source, bucket in metrics.by_source.items() %}
|
|
<tr>
|
|
<td><span style="font-size:0.8rem;padding:2px 8px;border-radius:4px;background:#f1f5f9;font-weight:600;">{{ source }}</span></td>
|
|
<td>{{ bucket.total }}</td>
|
|
<td style="color:#16a34a;">{{ bucket.correct }}</td>
|
|
<td style="color:#ca8a04;">{{ bucket.modifie }}</td>
|
|
<td style="color:#dc2626;">{{ bucket.supprime }}</td>
|
|
<td style="font-weight:600;">{{ "%.1f" | format(bucket.precision * 100) }}%</td>
|
|
<td style="font-weight:600;color:#dc2626;">{{ "%.1f" | format(bucket.hallucination * 100) }}%</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Top corrections DAS -->
|
|
{% if metrics.top_corrections %}
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<h3>Top corrections DAS (code pipeline → code DIM)</h3>
|
|
<table style="margin-top:0.75rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>Code pipeline</th>
|
|
<th></th>
|
|
<th>Code corrigé</th>
|
|
<th>Occurrences</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for (code_from, code_to), count in metrics.top_corrections %}
|
|
<tr>
|
|
<td><span style="font-family:monospace;font-weight:600;color:#dc2626;">{{ code_from }}</span></td>
|
|
<td style="color:#94a3b8;">→</td>
|
|
<td><span style="font-family:monospace;font-weight:600;color:#16a34a;">{{ code_to }}</span></td>
|
|
<td>{{ count }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Top corrections DP -->
|
|
{% if metrics.dp_corrections %}
|
|
<div class="card" style="margin-bottom:1rem;">
|
|
<h3>Top corrections DP (code pipeline → code DIM)</h3>
|
|
<table style="margin-top:0.75rem;">
|
|
<thead>
|
|
<tr>
|
|
<th>Code pipeline</th>
|
|
<th></th>
|
|
<th>Code corrigé</th>
|
|
<th>Occurrences</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for (code_from, code_to), count in metrics.dp_corrections %}
|
|
<tr>
|
|
<td><span style="font-family:monospace;font-weight:600;color:#dc2626;">{{ code_from }}</span></td>
|
|
<td style="color:#94a3b8;">→</td>
|
|
<td><span style="font-family:monospace;font-weight:600;color:#16a34a;">{{ code_to }}</span></td>
|
|
<td>{{ count }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% endif %}
|
|
{% endblock %}
|