| {% extends "base.html" %} |
|
|
| {% block title %}{{ row.image_id }} — Report{% endblock %} |
|
|
| {% block content %} |
| |
| <section class="breadcrumb"> |
| <a href="{{ url_for('home') }}">Home</a> |
| <span class="sep">/</span> |
| <a href="{{ url_for('reports') }}">Reports</a> |
| <span class="sep">/</span> |
| <span class="mono">{{ row.image_id }}</span> |
| </section> |
|
|
| |
| <section class="detail-header"> |
| <div> |
| <h1 class="mono">{{ row.image_id }}</h1> |
| <p> |
| {% if row.is_positive %} |
| <span class="dot dot-red"></span> {{ row.outcome }} |
| {% else %} |
| <span class="dot dot-green"></span> {{ row.outcome }} |
| {% endif %} |
| </p> |
| <p class="muted small" style="margin-top: 4px"> |
| {% if row.date_display != '—' %} |
| Generated: {{ row.date_display }} UTC |
| {% endif %} |
| {% if payload and payload.report_id %} |
| · Report ID: {{ payload.report_id }} |
| {% endif %} |
| </p> |
| </div> |
| <div class="detail-actions"> |
| {% if row.report_file %} |
| <a class="btn" |
| href="{{ url_for('serve_report_json', filename=row.report_file) }}" |
| target="_blank"> |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" |
| stroke="currentColor" stroke-width="2"> |
| <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> |
| <polyline points="14 2 14 8 20 8" /> |
| </svg> |
| Raw JSON |
| </a> |
| {% endif %} |
| <a class="btn" href="{{ url_for('reports') }}">← Back</a> |
| </div> |
| </section> |
|
|
| |
| <section class="detail-grid"> |
| |
| <article class="panel"> |
| <h3>Prediction Summary</h3> |
| <div class="kv-group"> |
| <div class="kv"> |
| <span>Screening Outcome</span> |
| <strong>{{ row.outcome }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Calibrated Probability</span> |
| <strong>{{ '%.4f'|format(row.cal_prob) if row.cal_prob is not none else 'N/A' }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Raw Probability</span> |
| <strong>{{ '%.4f'|format(row.raw_prob) if row.raw_prob is not none else 'N/A' }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Confidence Band</span> |
| <strong><span class="badge badge-{{ row.band|lower }}">{{ row.band }}</span></strong> |
| </div> |
| <div class="kv"> |
| <span>Triage Action</span> |
| <strong>{{ row.triage }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Urgency</span> |
| <strong><span class="badge badge-{{ row.urgency|lower }}">{{ row.urgency }}</span></strong> |
| </div> |
| <div class="kv"> |
| <span>Ground Truth</span> |
| <strong>{{ row.true_label if row.true_label and row.true_label != 'N/A' else 'Not available' }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Report Date</span> |
| <strong>{{ row.date_display }}</strong> |
| </div> |
| </div> |
|
|
| <form method="post" action="{{ url_for('update_ground_truth', image_id=row.image_id) }}" style="margin-top: 12px"> |
| <label class="muted small" for="trueLabel">Update Ground Truth</label> |
| <div class="dir-input-row" style="margin-top: 6px"> |
| <select name="true_label" id="trueLabel" class="input" style="max-width: 240px"> |
| <option value="" {% if not row.true_label %}selected{% endif %}>Not set</option> |
| <option value="POSITIVE" {% if row.true_label == 'POSITIVE' %}selected{% endif %}>Positive</option> |
| <option value="NEGATIVE" {% if row.true_label == 'NEGATIVE' %}selected{% endif %}>Negative</option> |
| <option value="UNKNOWN" {% if row.true_label == 'UNKNOWN' %}selected{% endif %}>Unknown</option> |
| </select> |
| <button type="submit" class="btn btn-primary" style="margin-left: 8px">Save</button> |
| </div> |
| </form> |
|
|
| |
| {% if row.cal_prob is not none %} |
| <div class="prob-bar-wrap"> |
| <div class="prob-bar-label"> |
| <span>0</span> |
| <span>Calibrated probability</span> |
| <span>1</span> |
| </div> |
| <div class="prob-bar"> |
| <div class="prob-fill {% if row.cal_prob >= 0.75 %}fill-high{% elif row.cal_prob >= 0.35 %}fill-medium{% else %}fill-low{% endif %}" |
| style="width: {{ (row.cal_prob * 100)|round(1) }}%"></div> |
| <div class="prob-marker" |
| style="left: {{ (row.cal_prob * 100)|round(1) }}%"> |
| {{ '%.2f'|format(row.cal_prob) }} |
| </div> |
| </div> |
| </div> |
| {% endif %} |
| </article> |
|
|
| |
| <article class="panel"> |
| <h3>Grad-CAM Visualization</h3> |
| {% if row.gradcam_url %} |
| <img class="heatmap-img" |
| src="{{ row.gradcam_url }}" |
| alt="Grad-CAM for {{ row.image_id }}" /> |
| <p class="muted small" style="margin-top: 10px"> |
| Highlighted regions indicate areas with greatest influence on the |
| model's decision. |
| </p> |
| {% else %} |
| <div class="empty-state"> |
| <p class="muted">No Grad-CAM heatmap available for this case.</p> |
| </div> |
| {% endif %} |
| </article> |
| </section> |
|
|
| |
| {% if report_record and report_record.llm_summary %} |
| <section class="panel" style="margin-bottom: 24px; border-left: 4px solid var(--primary, #007aff);"> |
| <h3 style="color: var(--primary, #007aff); display: flex; align-items: center; gap: 8px;"> |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/> |
| </svg> |
| AI Medical Intelligence Summary |
| </h3> |
| <p style="line-height: 1.6; font-size: 1.1rem; color: var(--text-dark, #2c3e50);"> |
| {{ report_record.llm_summary }} |
| </p> |
| </section> |
| {% endif %} |
|
|
| {% if payload and (payload.llm_provider or payload.llm_model) %} |
| <section class="panel" style="margin-top: 16px"> |
| <h3>LLM Information</h3> |
| <div class="kv-group" style="max-width: 500px"> |
| <div class="kv"> |
| <span>Provider</span> |
| <strong>{{ payload.llm_provider or 'N/A' }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Model</span> |
| <strong>{{ payload.llm_model or 'N/A' }}</strong> |
| </div> |
| </div> |
| </section> |
| {% endif %} |
|
|
| |
| {% if payload and payload.screening_module %} |
| <section class="panel" style="margin-top: 16px"> |
| <h3>Model Information</h3> |
| <div class="kv-group" style="max-width: 500px"> |
| <div class="kv"> |
| <span>Architecture</span> |
| <strong>{{ payload.screening_module.architecture }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Version</span> |
| <strong>{{ payload.screening_module.version }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Calibration</span> |
| <strong>{{ payload.screening_module.calibration_method }}</strong> |
| </div> |
| <div class="kv"> |
| <span>Decision Threshold</span> |
| <strong>{{ '%.4f'|format(payload.prediction.decision_threshold) }}</strong> |
| </div> |
| </div> |
| </section> |
| {% endif %} |
|
|
| |
| <section class="disclaimer-box"> |
| <strong>Disclaimer:</strong> |
| This report is produced by an AI-assisted screening tool and does NOT |
| constitute a medical diagnosis. All screening findings must be reviewed and |
| confirmed by a qualified, licensed medical professional before any clinical |
| decision is made. |
| </section> |
| {% endblock %} |
|
|