Files
t2a_v2/src/viewer/templates/validation_metrics.html
dom dbc5bdbaf4 feat: mode Validation DIM dans le viewer Flask
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>
2026-02-17 21:43:02 +01:00

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">&#9733; 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 &rarr; 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;">&rarr;</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 &rarr; 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;">&rarr;</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 %}