feat: viewer — affichage qualité CPAM, traçabilité décisions DP/DAS, VetoReport et bio
- CPAM : badge quality_tier (A/B/C), bandeau requires_review, warnings catégorisés, force probante dossier - DP/DAS : code suggestion barré → code final si modifié, ligne grisée si ruled_out, badges décision + règles - DAS : badge needs_info avec détails, raison ruled_out sous la ligne - VetoReport : section contestabilité avec verdict, barre score/100, tableau issues HARD/MEDIUM/LOW - Biologie : badge Suspect avec tooltip, valeurs écartées en details pliable - Nouveau filtre Jinja2 decision_badge, import _assess_dossier_strength (pas de duplication) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ from ..config import (
|
|||||||
CIM10_PDF, GUIDE_METHODO_PDF, CCAM_PDF, CIM10_DICT_PATH, CIM10_SUPPLEMENTS_PATH,
|
CIM10_PDF, GUIDE_METHODO_PDF, CCAM_PDF, CIM10_DICT_PATH, CIM10_SUPPLEMENTS_PATH,
|
||||||
)
|
)
|
||||||
from .. import config as cfg
|
from .. import config as cfg
|
||||||
|
from ..control.cpam_context import _assess_dossier_strength
|
||||||
from .referentiels import ReferentielManager
|
from .referentiels import ReferentielManager
|
||||||
from .validation import ValidationManager
|
from .validation import ValidationManager
|
||||||
|
|
||||||
@@ -374,6 +375,24 @@ def format_doc_name(name: str) -> str:
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
|
def decision_badge(decision) -> Markup:
|
||||||
|
"""Badge HTML pour une CodeDecision (action != KEEP)."""
|
||||||
|
if not decision:
|
||||||
|
return Markup("")
|
||||||
|
action = decision.get("action", "KEEP") if isinstance(decision, dict) else getattr(decision, "action", "KEEP")
|
||||||
|
if action == "KEEP":
|
||||||
|
return Markup("")
|
||||||
|
labels = {
|
||||||
|
"DOWNGRADE": ("Rétrogradé", "#fef3c7", "#92400e"),
|
||||||
|
"REMOVE": ("Supprimé", "#fee2e2", "#dc2626"),
|
||||||
|
"RULED_OUT": ("Écarté", "#f1f5f9", "#64748b"),
|
||||||
|
"NEED_INFO": ("Info requise", "#fff7ed", "#c2410c"),
|
||||||
|
"PROMOTE_DP": ("Promu DP", "#dbeafe", "#1d4ed8"),
|
||||||
|
}
|
||||||
|
label, bg, fg = labels.get(action, (action, "#f1f5f9", "#64748b"))
|
||||||
|
return Markup(f'<span class="badge" style="background:{bg};color:{fg};font-size:0.7rem;">{label}</span>')
|
||||||
|
|
||||||
|
|
||||||
def format_cpam_text(text: str | None) -> Markup:
|
def format_cpam_text(text: str | None) -> Markup:
|
||||||
"""Convertit un texte CPAM (section) en HTML avec puces et paragraphes."""
|
"""Convertit un texte CPAM (section) en HTML avec puces et paragraphes."""
|
||||||
if not text:
|
if not text:
|
||||||
@@ -420,6 +439,7 @@ def create_app() -> Flask:
|
|||||||
app.jinja_env.filters["format_dossier_name"] = format_dossier_name
|
app.jinja_env.filters["format_dossier_name"] = format_dossier_name
|
||||||
app.jinja_env.filters["format_doc_name"] = format_doc_name
|
app.jinja_env.filters["format_doc_name"] = format_doc_name
|
||||||
app.jinja_env.filters["format_cpam_text"] = format_cpam_text
|
app.jinja_env.filters["format_cpam_text"] = format_cpam_text
|
||||||
|
app.jinja_env.filters["decision_badge"] = decision_badge
|
||||||
|
|
||||||
ccam_dict = load_ccam_dict()
|
ccam_dict = load_ccam_dict()
|
||||||
|
|
||||||
@@ -440,6 +460,8 @@ def create_app() -> Flask:
|
|||||||
if len(rel_parts) > 1:
|
if len(rel_parts) > 1:
|
||||||
current_group = str(Path(*rel_parts[:-1]))
|
current_group = str(Path(*rel_parts[:-1]))
|
||||||
siblings = groups.get(current_group, [])
|
siblings = groups.get(current_group, [])
|
||||||
|
# Force probante (pour section CPAM)
|
||||||
|
dossier_strength = _assess_dossier_strength(dossier) if dossier.controles_cpam else None
|
||||||
return render_template(
|
return render_template(
|
||||||
"detail.html",
|
"detail.html",
|
||||||
dossier=dossier,
|
dossier=dossier,
|
||||||
@@ -447,6 +469,7 @@ def create_app() -> Flask:
|
|||||||
ccam_dict=ccam_dict,
|
ccam_dict=ccam_dict,
|
||||||
siblings=siblings,
|
siblings=siblings,
|
||||||
current_group=current_group,
|
current_group=current_group,
|
||||||
|
dossier_strength=dossier_strength,
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.route("/dashboard")
|
@app.route("/dashboard")
|
||||||
|
|||||||
@@ -131,9 +131,14 @@
|
|||||||
{% if dossier.controles_cpam %}
|
{% if dossier.controles_cpam %}
|
||||||
<div class="card section" style="border-left:4px solid #f59e0b;">
|
<div class="card section" style="border-left:4px solid #f59e0b;">
|
||||||
<h3 style="color:#b45309;">Contrôle CPAM ({{ dossier.controles_cpam|length }})</h3>
|
<h3 style="color:#b45309;">Contrôle CPAM ({{ dossier.controles_cpam|length }})</h3>
|
||||||
|
{% if dossier_strength and dossier_strength.is_weak %}
|
||||||
|
<div style="background:#fff7ed;border:1px solid #fed7aa;padding:0.5rem 0.75rem;border-radius:4px;margin-bottom:0.75rem;font-size:0.85rem;color:#9a3412;">
|
||||||
|
Dossier à preuves limitées (score {{ dossier_strength.score }}/10) — manque : {{ dossier_strength.missing|join(', ') }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% for ctrl in dossier.controles_cpam %}
|
{% for ctrl in dossier.controles_cpam %}
|
||||||
<div style="margin-bottom:1.5rem;{% if not loop.last %}border-bottom:1px solid #e2e8f0;padding-bottom:1rem;{% endif %}">
|
<div style="margin-bottom:1.5rem;{% if not loop.last %}border-bottom:1px solid #e2e8f0;padding-bottom:1rem;{% endif %}">
|
||||||
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;">
|
<div style="display:flex;align-items:center;gap:0.5rem;margin-bottom:0.5rem;flex-wrap:wrap;">
|
||||||
<strong>OGC {{ ctrl.numero_ogc }} — {{ ctrl.titre }}</strong>
|
<strong>OGC {{ ctrl.numero_ogc }} — {{ ctrl.titre }}</strong>
|
||||||
{% if 'retient' in ctrl.decision_ucr|lower %}
|
{% if 'retient' in ctrl.decision_ucr|lower %}
|
||||||
<span class="badge" style="background:#d1fae5;color:#065f46;">{{ ctrl.decision_ucr }}</span>
|
<span class="badge" style="background:#d1fae5;color:#065f46;">{{ ctrl.decision_ucr }}</span>
|
||||||
@@ -142,6 +147,13 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge" style="background:#e0e7ff;color:#3730a3;">{{ ctrl.decision_ucr }}</span>
|
<span class="badge" style="background:#e0e7ff;color:#3730a3;">{{ ctrl.decision_ucr }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if ctrl.quality_tier == 'A' %}
|
||||||
|
<span class="badge" style="background:#2ecc71;color:#fff;font-weight:700;">Qualité A</span>
|
||||||
|
{% elif ctrl.quality_tier == 'B' %}
|
||||||
|
<span class="badge" style="background:#f39c12;color:#fff;font-weight:700;">Qualité B</span>
|
||||||
|
{% elif ctrl.quality_tier == 'C' %}
|
||||||
|
<span class="badge" style="background:#e74c3c;color:#fff;font-weight:700;">Qualité C</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Argument CPAM #}
|
{# Argument CPAM #}
|
||||||
@@ -165,6 +177,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Bandeau revue manuelle si Tier C #}
|
||||||
|
{% if ctrl.requires_review %}
|
||||||
|
<div style="background:#fee2e2;border:1px solid #fca5a5;padding:0.5rem 0.75rem;border-radius:4px;margin-bottom:0.75rem;font-size:0.85rem;color:#991b1b;">
|
||||||
|
⚠ Revue manuelle requise — la contre-argumentation contient des incohérences détectées
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# Contre-argumentation structurée ou fallback texte brut #}
|
{# Contre-argumentation structurée ou fallback texte brut #}
|
||||||
{% if ctrl.response_data %}
|
{% if ctrl.response_data %}
|
||||||
<div style="margin-bottom:0.75rem;">
|
<div style="margin-bottom:0.75rem;">
|
||||||
@@ -261,6 +280,22 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</details>
|
</details>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Avertissements qualité #}
|
||||||
|
{% if ctrl.quality_warnings %}
|
||||||
|
<details style="margin-top:0.5rem;">
|
||||||
|
<summary style="font-size:0.8rem;color:#9333ea;">Avertissements qualité ({{ ctrl.quality_warnings|length }})</summary>
|
||||||
|
<ul style="margin:0.25rem 0;padding-left:1.2rem;">
|
||||||
|
{% for w in ctrl.quality_warnings %}
|
||||||
|
{% if w.startswith('[CRITIQUE]') %}
|
||||||
|
<li style="color:#dc2626;font-size:0.8rem;">{{ w }}</li>
|
||||||
|
{% else %}
|
||||||
|
<li style="color:#d97706;font-size:0.8rem;">{{ w }}</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@@ -282,17 +317,75 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# ---- Contestabilité (VetoReport) ---- #}
|
||||||
|
{% if dossier.veto_report %}
|
||||||
|
{% set vr = dossier.veto_report %}
|
||||||
|
{% if vr.verdict == 'PASS' %}
|
||||||
|
{% set vr_color = '#22c55e' %}
|
||||||
|
{% elif vr.verdict == 'NEED_INFO' %}
|
||||||
|
{% set vr_color = '#f59e0b' %}
|
||||||
|
{% else %}
|
||||||
|
{% set vr_color = '#ef4444' %}
|
||||||
|
{% endif %}
|
||||||
|
<div class="card section" style="border-left:4px solid {{ vr_color }};">
|
||||||
|
<h3>Contestabilité du dossier</h3>
|
||||||
|
<div style="display:flex;align-items:center;gap:1rem;">
|
||||||
|
{% if vr.verdict == 'PASS' %}
|
||||||
|
<span class="badge" style="background:#d1fae5;color:#065f46;font-weight:700;">PASS</span>
|
||||||
|
{% elif vr.verdict == 'NEED_INFO' %}
|
||||||
|
<span class="badge" style="background:#fef3c7;color:#92400e;font-weight:700;">NEED_INFO</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge" style="background:#fee2e2;color:#dc2626;font-weight:700;">FAIL</span>
|
||||||
|
{% endif %}
|
||||||
|
<div style="flex:1;height:8px;background:#e2e8f0;border-radius:4px;">
|
||||||
|
<div style="width:{{ vr.score_contestabilite }}%;height:100%;background:{{ vr_color }};border-radius:4px;"></div>
|
||||||
|
</div>
|
||||||
|
<span style="font-weight:600;">{{ vr.score_contestabilite }}/100</span>
|
||||||
|
</div>
|
||||||
|
{% if vr.issues %}
|
||||||
|
<details style="margin-top:0.5rem;">
|
||||||
|
<summary style="font-size:0.8rem;color:#64748b;">Problèmes détectés ({{ vr.issues|length }})</summary>
|
||||||
|
<table style="margin-top:0.25rem;">
|
||||||
|
<thead><tr><th>Veto</th><th>Sévérité</th><th>Localisation</th><th>Message</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for issue in vr.issues %}
|
||||||
|
<tr>
|
||||||
|
<td><code style="font-size:0.75rem;">{{ issue.veto }}</code></td>
|
||||||
|
<td>
|
||||||
|
{% if issue.severity == 'HARD' %}<span class="badge" style="background:#fee2e2;color:#dc2626;">HARD</span>
|
||||||
|
{% elif issue.severity == 'MEDIUM' %}<span class="badge" style="background:#fef3c7;color:#92400e;">MEDIUM</span>
|
||||||
|
{% else %}<span class="badge" style="background:#f0fdf4;color:#166534;">LOW</span>{% endif %}
|
||||||
|
</td>
|
||||||
|
<td style="font-size:0.75rem;color:#64748b;">{{ issue.where }}</td>
|
||||||
|
<td style="font-size:0.8rem;">{{ issue.message }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{# ---- Diagnostic principal ---- #}
|
{# ---- Diagnostic principal ---- #}
|
||||||
{% if dossier.diagnostic_principal %}
|
{% if dossier.diagnostic_principal %}
|
||||||
{% set dp = dossier.diagnostic_principal %}
|
{% set dp = dossier.diagnostic_principal %}
|
||||||
<div class="card section">
|
<div class="card section"{% if dp.status == 'ruled_out' %} style="opacity:0.5;"{% endif %}>
|
||||||
<h3>Diagnostic principal</h3>
|
<h3>Diagnostic principal</h3>
|
||||||
<div style="font-size:0.95rem;margin-bottom:0.5rem;">
|
<div style="font-size:0.95rem;margin-bottom:0.5rem;">
|
||||||
{{ dp.texte }}
|
{% if dp.status == 'ruled_out' %}<span style="text-decoration:line-through;">{{ dp.texte }}</span>{% else %}{{ dp.texte }}{% endif %}
|
||||||
{% if dp.source_page %}<button class="src-btn" data-texte="{{ dp.texte|e }}" data-excerpt="{{ dp.source_excerpt|default('',true)|e }}" data-page="{{ dp.source_page }}">p.{{ dp.source_page }}</button>{% endif %}
|
{% if dp.source_page %}<button class="src-btn" data-texte="{{ dp.texte|e }}" data-excerpt="{{ dp.source_excerpt|default('',true)|e }}" data-page="{{ dp.source_page }}">p.{{ dp.source_page }}</button>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if dp.cim10_suggestion %}
|
{% if dp.cim10_suggestion %}
|
||||||
<span class="badge" style="background:#dbeafe;color:#1d4ed8;font-size:0.85rem;">{{ dp.cim10_suggestion }}</span>
|
{% if dp.cim10_final and dp.cim10_final != dp.cim10_suggestion %}
|
||||||
|
<span style="text-decoration:line-through;color:#94a3b8;font-size:0.85rem;">{{ dp.cim10_suggestion }}</span>
|
||||||
|
<span style="color:#64748b;">→</span>
|
||||||
|
<span class="badge" style="background:#dbeafe;color:#1d4ed8;font-size:0.85rem;">{{ dp.cim10_final }}</span>
|
||||||
|
{% elif dp.status == 'ruled_out' %}
|
||||||
|
<span style="text-decoration:line-through;color:#94a3b8;font-size:0.85rem;">{{ dp.cim10_suggestion }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge" style="background:#dbeafe;color:#1d4ed8;font-size:0.85rem;">{{ dp.cim10_suggestion }}</span>
|
||||||
|
{% endif %}
|
||||||
{{ dp.cim10_confidence | confidence_badge }}
|
{{ dp.cim10_confidence | confidence_badge }}
|
||||||
{% if dp.niveau_cma and dp.niveau_cma > 1 %}
|
{% if dp.niveau_cma and dp.niveau_cma > 1 %}
|
||||||
{{ dp.niveau_cma | cma_level_badge }}
|
{{ dp.niveau_cma | cma_level_badge }}
|
||||||
@@ -300,6 +393,18 @@
|
|||||||
<span class="badge" style="background:#fee2e2;color:#dc2626;font-size:0.75rem;">CMA</span>
|
<span class="badge" style="background:#fee2e2;color:#dc2626;font-size:0.75rem;">CMA</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ dp.niveau_severite | severity_badge }}
|
{{ dp.niveau_severite | severity_badge }}
|
||||||
|
{% if dp.cim10_decision and dp.cim10_decision.action != 'KEEP' %}
|
||||||
|
{{ dp.cim10_decision | decision_badge }}
|
||||||
|
{% for rule in dp.cim10_decision.applied_rules %}
|
||||||
|
<span class="badge" style="background:#f1f5f9;color:#64748b;font-size:0.65rem;">{{ rule }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if dp.status == 'ruled_out' and dp.ruled_out_reason %}
|
||||||
|
<div style="font-size:0.75rem;color:#dc2626;margin-top:0.25rem;">{{ dp.ruled_out_reason }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if dp.cim10_decision and dp.cim10_decision.action != 'KEEP' and dp.cim10_decision.reason %}
|
||||||
|
<div style="font-size:0.75rem;color:#64748b;margin-top:0.25rem;">{{ dp.cim10_decision.reason }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if dp.justification %}
|
{% if dp.justification %}
|
||||||
<div style="margin-top:0.5rem;font-size:0.8rem;color:#475569;">{{ dp.justification }}</div>
|
<div style="margin-top:0.5rem;font-size:0.8rem;color:#475569;">{{ dp.justification }}</div>
|
||||||
@@ -340,9 +445,43 @@
|
|||||||
<thead><tr><th>Texte</th><th>CIM-10</th><th>Confiance</th><th>CMA</th><th>Source</th><th>Justification</th></tr></thead>
|
<thead><tr><th>Texte</th><th>CIM-10</th><th>Confiance</th><th>CMA</th><th>Source</th><th>Justification</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for das in dossier.diagnostics_associes %}
|
{% for das in dossier.diagnostics_associes %}
|
||||||
<tr>
|
<tr{% if das.status == 'ruled_out' %} style="opacity:0.5;text-decoration:line-through;"{% endif %}>
|
||||||
<td>{{ das.texte }}</td>
|
<td>{{ das.texte }}</td>
|
||||||
<td>{% if das.cim10_suggestion %}<span class="badge" style="background:#dbeafe;color:#1d4ed8;">{{ das.cim10_suggestion }}</span>{% endif %}</td>
|
<td>
|
||||||
|
{% if das.cim10_suggestion %}
|
||||||
|
{% if das.cim10_final and das.cim10_final != das.cim10_suggestion %}
|
||||||
|
<span style="text-decoration:line-through;color:#94a3b8;font-size:0.8rem;">{{ das.cim10_suggestion }}</span>
|
||||||
|
<span style="color:#64748b;">→</span>
|
||||||
|
<span class="badge" style="background:#dbeafe;color:#1d4ed8;">{{ das.cim10_final }}</span>
|
||||||
|
{% elif das.status == 'ruled_out' %}
|
||||||
|
<span style="color:#94a3b8;">{{ das.cim10_suggestion }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge" style="background:#dbeafe;color:#1d4ed8;">{{ das.cim10_suggestion }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if das.cim10_decision and das.cim10_decision.action != 'KEEP' %}
|
||||||
|
<div style="margin-top:0.2rem;">
|
||||||
|
{{ das.cim10_decision | decision_badge }}
|
||||||
|
{% for rule in das.cim10_decision.applied_rules %}
|
||||||
|
<span class="badge" style="background:#f1f5f9;color:#64748b;font-size:0.65rem;">{{ rule }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if das.status == 'needs_info' %}
|
||||||
|
<div style="margin-top:0.2rem;">
|
||||||
|
<span class="badge" style="background:#fff7ed;color:#c2410c;font-size:0.7rem;">Info requise</span>
|
||||||
|
{% if das.cim10_decision and das.cim10_decision.needs_info %}
|
||||||
|
<details style="margin-top:0.15rem;"><summary style="font-size:0.7rem;color:#c2410c;cursor:pointer;">détails</summary>
|
||||||
|
<ul style="margin:0.1rem 0;padding-left:1rem;font-size:0.7rem;color:#9a3412;">
|
||||||
|
{% for ni in das.cim10_decision.needs_info %}
|
||||||
|
<li>{{ ni }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ das.cim10_confidence | confidence_badge }}</td>
|
<td>{{ das.cim10_confidence | confidence_badge }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% if das.niveau_cma and das.niveau_cma > 1 %}
|
{% if das.niveau_cma and das.niveau_cma > 1 %}
|
||||||
@@ -374,6 +513,20 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if das.status == 'ruled_out' and das.ruled_out_reason %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="padding:0 0.75rem 0.3rem;">
|
||||||
|
<div style="font-size:0.75rem;color:#dc2626;">{{ das.ruled_out_reason }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% if das.cim10_decision and das.cim10_decision.action != 'KEEP' and das.cim10_decision.reason and das.status != 'ruled_out' %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="padding:0 0.75rem 0.3rem;">
|
||||||
|
<div style="font-size:0.75rem;color:#64748b;">{{ das.cim10_decision.reason }}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
{% if das.raisonnement %}
|
{% if das.raisonnement %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="6" style="padding:0 0.75rem 0.5rem;">
|
<td colspan="6" style="padding:0 0.75rem 0.5rem;">
|
||||||
@@ -446,15 +599,36 @@
|
|||||||
<thead><tr><th>Test</th><th>Valeur</th><th>Anomalie</th><th>Source</th></tr></thead>
|
<thead><tr><th>Test</th><th>Valeur</th><th>Anomalie</th><th>Source</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for b in dossier.biologie_cle %}
|
{% for b in dossier.biologie_cle %}
|
||||||
<tr{% if b.anomalie %} class="anomalie"{% endif %}>
|
<tr{% if b.quality == 'suspect' %} style="background:#fffbeb;"{% elif b.anomalie %} class="anomalie"{% endif %}>
|
||||||
<td>{{ b.test }}</td>
|
<td>{{ b.test }}</td>
|
||||||
<td>{{ b.valeur or '' }}</td>
|
<td>{{ b.valeur or '' }}</td>
|
||||||
<td>{% if b.anomalie %}<span class="badge" style="background:#fee2e2;color:#dc2626;">Oui</span>{% else %}—{% endif %}</td>
|
<td>
|
||||||
|
{% if b.quality == 'suspect' %}
|
||||||
|
<span class="badge" style="background:#fef3c7;color:#92400e;" title="{{ b.discard_reason or '' }}">⚠ Suspect</span>
|
||||||
|
{% elif b.anomalie %}
|
||||||
|
<span class="badge" style="background:#fee2e2;color:#dc2626;">Oui</span>
|
||||||
|
{% else %}
|
||||||
|
—
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{% if b.source_page %}<button class="src-btn" data-texte="{{ b.test|e }}" data-excerpt="{{ b.source_excerpt|default('',true)|e }}" data-page="{{ b.source_page }}">p.{{ b.source_page }}</button>{% endif %}</td>
|
<td>{% if b.source_page %}<button class="src-btn" data-texte="{{ b.test|e }}" data-excerpt="{{ b.source_excerpt|default('',true)|e }}" data-page="{{ b.source_page }}">p.{{ b.source_page }}</button>{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
{% if dossier.biologie_discarded %}
|
||||||
|
<details style="margin-top:0.5rem;">
|
||||||
|
<summary style="font-size:0.8rem;color:#d97706;">Valeurs écartées ({{ dossier.biologie_discarded|length }})</summary>
|
||||||
|
<table style="margin-top:0.25rem;">
|
||||||
|
<thead><tr><th>Test</th><th>Valeur</th><th>Raison</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{% for b in dossier.biologie_discarded %}
|
||||||
|
<tr style="opacity:0.6;"><td>{{ b.test or '' }}</td><td>{{ b.valeur or '' }}</td><td>{{ b.discard_reason or '—' }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user