618 lines
19 KiB
HTML
618 lines
19 KiB
HTML
{% load i18n %}<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>{{ report.kpi_id }} - {{ report.indicator_title }} - PX360</title>
|
|
<style>
|
|
@page {
|
|
size: A4 landscape;
|
|
margin: 1.5cm;
|
|
@top-center {
|
|
content: "{{ report.hospital.name }} — KPI Report {{ report.kpi_id }}";
|
|
font-size: 8pt;
|
|
color: #94a3b8;
|
|
}
|
|
@bottom-right {
|
|
content: "Page " counter(page) " of " counter(pages);
|
|
font-size: 8pt;
|
|
color: #94a3b8;
|
|
}
|
|
@bottom-left {
|
|
content: "Generated {{ generated_at }}";
|
|
font-size: 8pt;
|
|
color: #94a3b8;
|
|
}
|
|
}
|
|
@page :first {
|
|
@top-center { content: ""; }
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
|
font-size: 9pt;
|
|
line-height: 1.5;
|
|
color: #1f2937;
|
|
}
|
|
|
|
/* Header */
|
|
.report-header {
|
|
border-bottom: 3px solid #005696;
|
|
padding-bottom: 12px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
}
|
|
.report-header .header-info {
|
|
flex: 1;
|
|
}
|
|
.report-header .header-logo {
|
|
height: 50px;
|
|
width: auto;
|
|
flex-shrink: 0;
|
|
margin-left: 16px;
|
|
}
|
|
.report-header h1 {
|
|
font-size: 18pt;
|
|
color: #005696;
|
|
margin: 0;
|
|
}
|
|
.report-header .kpi-badge {
|
|
display: inline-block;
|
|
background: #005696;
|
|
color: white;
|
|
padding: 3px 10px;
|
|
border-radius: 4px;
|
|
font-size: 10pt;
|
|
font-weight: bold;
|
|
margin-bottom: 6px;
|
|
}
|
|
.report-header .subtitle {
|
|
font-size: 10pt;
|
|
color: #64748b;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* Summary cards */
|
|
.summary-grid {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.summary-card {
|
|
flex: 1;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 12px;
|
|
text-align: center;
|
|
}
|
|
.summary-card .label {
|
|
font-size: 8pt;
|
|
color: #64748b;
|
|
text-transform: uppercase;
|
|
font-weight: bold;
|
|
margin-bottom: 4px;
|
|
}
|
|
.summary-card .value {
|
|
font-size: 20pt;
|
|
font-weight: bold;
|
|
color: #005696;
|
|
}
|
|
.summary-card .value.danger { color: #dc2626; }
|
|
.summary-card .value.success { color: #16a34a; }
|
|
.summary-card .detail {
|
|
font-size: 8pt;
|
|
color: #94a3b8;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
/* Progress bar */
|
|
.progress-container {
|
|
margin-bottom: 20px;
|
|
padding: 12px;
|
|
background: #f8fafc;
|
|
border-radius: 8px;
|
|
}
|
|
.progress-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
font-size: 8pt;
|
|
font-weight: bold;
|
|
color: #64748b;
|
|
text-transform: uppercase;
|
|
margin-bottom: 6px;
|
|
}
|
|
.progress-bar {
|
|
height: 12px;
|
|
background: #e2e8f0;
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
.progress-fill {
|
|
height: 100%;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* Tables */
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-bottom: 20px;
|
|
}
|
|
table th {
|
|
background: #005696;
|
|
color: white;
|
|
font-size: 8pt;
|
|
font-weight: bold;
|
|
text-transform: uppercase;
|
|
padding: 6px 4px;
|
|
text-align: center;
|
|
border: 1px solid #005696;
|
|
}
|
|
table td {
|
|
padding: 5px 4px;
|
|
border: 1px solid #e2e8f0;
|
|
font-size: 8.5pt;
|
|
text-align: center;
|
|
}
|
|
table tr:nth-child(even) td { background: #f8fafc; }
|
|
table .row-label {
|
|
text-align: left;
|
|
font-weight: bold;
|
|
color: #1f2937;
|
|
background: #f1f5f9 !important;
|
|
}
|
|
table .total-row td {
|
|
background: #005696 !important;
|
|
color: white !important;
|
|
font-weight: bold;
|
|
}
|
|
table .below-target {
|
|
color: #dc2626;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Metadata grid */
|
|
.metadata-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-bottom: 20px;
|
|
}
|
|
.metadata-item {
|
|
background: #f8fafc;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 6px;
|
|
padding: 6px 12px;
|
|
min-width: 120px;
|
|
}
|
|
.metadata-item .label {
|
|
font-size: 7pt;
|
|
color: #94a3b8;
|
|
text-transform: uppercase;
|
|
font-weight: bold;
|
|
}
|
|
.metadata-item .value {
|
|
font-size: 9pt;
|
|
color: #1f2937;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Charts */
|
|
.charts-row {
|
|
display: flex;
|
|
gap: 20px;
|
|
margin-bottom: 20px;
|
|
page-break-inside: avoid;
|
|
}
|
|
.chart-box {
|
|
flex: 1;
|
|
text-align: center;
|
|
}
|
|
.chart-box img {
|
|
max-width: 100%;
|
|
height: auto;
|
|
}
|
|
|
|
/* Breakdown cards */
|
|
.breakdown-grid {
|
|
display: flex;
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.breakdown-card {
|
|
flex: 1;
|
|
min-width: 180px;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 10px;
|
|
page-break-inside: avoid;
|
|
}
|
|
.breakdown-card h4 {
|
|
font-size: 9pt;
|
|
color: #005696;
|
|
margin-bottom: 6px;
|
|
border-bottom: 1px solid #e2e8f0;
|
|
padding-bottom: 4px;
|
|
}
|
|
.breakdown-card .stat {
|
|
font-size: 8.5pt;
|
|
color: #64748b;
|
|
margin-bottom: 2px;
|
|
}
|
|
.breakdown-card .stat strong { color: #1f2937; }
|
|
|
|
/* Section heading */
|
|
.section-title {
|
|
font-size: 11pt;
|
|
color: #005696;
|
|
font-weight: bold;
|
|
margin-bottom: 10px;
|
|
margin-top: 20px;
|
|
border-left: 4px solid #005696;
|
|
padding-left: 8px;
|
|
}
|
|
|
|
/* AI Analysis */
|
|
.ai-section {
|
|
background: #f8fafc;
|
|
border: 1px solid #e2e8f0;
|
|
border-radius: 8px;
|
|
padding: 14px;
|
|
margin-bottom: 20px;
|
|
page-break-inside: avoid;
|
|
}
|
|
.ai-section h4 {
|
|
font-size: 9pt;
|
|
color: #005696;
|
|
margin-bottom: 6px;
|
|
text-transform: uppercase;
|
|
font-weight: bold;
|
|
}
|
|
.ai-section p { font-size: 9pt; margin-bottom: 8px; }
|
|
.ai-section ul { padding-left: 18px; margin-bottom: 8px; }
|
|
.ai-section li { font-size: 9pt; margin-bottom: 3px; }
|
|
|
|
.findings-box {
|
|
background: white;
|
|
border-left: 3px solid #007bbd;
|
|
padding: 8px 12px;
|
|
margin-bottom: 8px;
|
|
border-radius: 0 4px 4px 0;
|
|
}
|
|
|
|
.recommendation-box {
|
|
background: #ecfdf5;
|
|
border-left: 3px solid #16a34a;
|
|
padding: 8px 12px;
|
|
margin-bottom: 8px;
|
|
border-radius: 0 4px 4px 0;
|
|
}
|
|
|
|
/* Sign-off */
|
|
.signoff-grid {
|
|
display: flex;
|
|
gap: 16px;
|
|
margin-top: 30px;
|
|
page-break-inside: avoid;
|
|
}
|
|
.signoff-box {
|
|
flex: 1;
|
|
border: 1px solid #cbd5e1;
|
|
border-radius: 8px;
|
|
padding: 20px 12px 12px 12px;
|
|
min-height: 80px;
|
|
}
|
|
.signoff-box .role {
|
|
font-size: 8pt;
|
|
color: #64748b;
|
|
text-transform: uppercase;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
.signoff-box .line {
|
|
border-top: 1px solid #94a3b8;
|
|
margin-bottom: 4px;
|
|
}
|
|
.signoff-box .line-label {
|
|
font-size: 7pt;
|
|
color: #94a3b8;
|
|
}
|
|
|
|
/* Footer */
|
|
.report-footer {
|
|
margin-top: 30px;
|
|
padding-top: 10px;
|
|
border-top: 1px solid #e2e8f0;
|
|
font-size: 8pt;
|
|
color: #94a3b8;
|
|
text-align: center;
|
|
}
|
|
|
|
.page-break { page-break-before: always; }
|
|
.no-break { page-break-inside: avoid; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Report Header -->
|
|
<div class="report-header">
|
|
<div class="header-info">
|
|
<div class="kpi-badge">{{ report.kpi_id }}</div>
|
|
<h1>{{ report.indicator_title }}</h1>
|
|
<div class="subtitle">
|
|
{{ report.hospital.name }} |
|
|
{{ report.report_period_display }} |
|
|
{% trans "Generated" %}: {{ report.generated_at|default:report.created_at|date:"M d, Y H:i" }}
|
|
</div>
|
|
</div>
|
|
{% if logo_path %}
|
|
<img src="{{ logo_path }}" alt="{{ report.hospital.name }}" class="header-logo">
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Executive Summary Cards -->
|
|
<div class="summary-grid">
|
|
<div class="summary-card">
|
|
<div class="label">{% trans "Overall Result" %}</div>
|
|
<div class="value {% if report.overall_result < report.threshold_percentage %}danger{% elif report.overall_result >= report.target_percentage %}success{% endif %}">
|
|
{{ report.overall_result|floatformat:1 }}%
|
|
</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">{{ report.numerator_label }}</div>
|
|
<div class="value">{{ report.total_numerator }}</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">{{ report.denominator_label }}</div>
|
|
<div class="value">{{ report.total_denominator }}</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">{% trans "Target" %}</div>
|
|
<div class="value" style="color:#16a34a;">{{ report.target_percentage|floatformat:0 }}%</div>
|
|
</div>
|
|
<div class="summary-card">
|
|
<div class="label">{% trans "Threshold" %}</div>
|
|
<div class="value" style="color:#dc2626;">{{ report.threshold_percentage|floatformat:0 }}%</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress Bar -->
|
|
<div class="progress-container">
|
|
<div class="progress-label">
|
|
<span>{% trans "Performance vs Target" %}</span>
|
|
<span>{{ report.overall_result|floatformat:1 }}% / {{ report.target_percentage|floatformat:0 }}%</span>
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" style="width: {{ report.overall_result|floatformat:0|stringformat:'s' }}%; background: {% if report.overall_result < report.threshold_percentage %}#dc2626{% elif report.overall_result >= report.target_percentage %}#16a34a{% else %}#f59e0b{% endif %};"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Monthly Data Table -->
|
|
<div class="section-title">{% trans "Monthly Performance Data" %}</div>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th style="width:140px;">{% trans "Month" %}</th>
|
|
<th>Jan</th><th>Feb</th><th>Mar</th><th>Apr</th>
|
|
<th>May</th><th>Jun</th><th>Jul</th><th>Aug</th>
|
|
<th>Sep</th><th>Oct</th><th>Nov</th><th>Dec</th>
|
|
<th style="background:#003d5c;">{% trans "TOTAL" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<!-- Numerator row -->
|
|
<tr>
|
|
<td class="row-label">{{ report.numerator_label }}</td>
|
|
{% for m in monthly_data %}<td>{% if m %}{{ m.numerator }}{% else %}-{% endif %}</td>{% endfor %}
|
|
<td>{{ report.total_numerator }}</td>
|
|
</tr>
|
|
<!-- Denominator row -->
|
|
<tr>
|
|
<td class="row-label">{{ report.denominator_label }}</td>
|
|
{% for m in monthly_data %}<td>{% if m %}{{ m.denominator }}{% else %}-{% endif %}</td>{% endfor %}
|
|
<td>{{ report.total_denominator }}</td>
|
|
</tr>
|
|
<!-- Result % row -->
|
|
<tr>
|
|
<td class="row-label">{% trans "Result %" %}</td>
|
|
{% for m in monthly_data %}
|
|
<td {% if m and m.percentage < report.threshold_percentage %}class="below-target"{% endif %}>
|
|
{% if m %}{{ m.percentage|floatformat:1 }}{% else %}-{% endif %}
|
|
</td>
|
|
{% endfor %}
|
|
<td><strong>{{ report.overall_result|floatformat:1 }}</strong></td>
|
|
</tr>
|
|
<!-- Target row -->
|
|
<tr>
|
|
<td class="row-label">{% trans "Target" %}</td>
|
|
<td colspan="12">{{ report.target_percentage|floatformat:0 }}%</td>
|
|
<td>{{ report.target_percentage|floatformat:0 }}%</td>
|
|
</tr>
|
|
<!-- Threshold row -->
|
|
<tr>
|
|
<td class="row-label">{% trans "Threshold" %}</td>
|
|
<td colspan="12">{{ report.threshold_percentage|floatformat:0 }}%</td>
|
|
<td>{{ report.threshold_percentage|floatformat:0 }}%</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- Metadata -->
|
|
<div class="metadata-grid">
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Category" %}</div>
|
|
<div class="value">{{ report.category }}</div>
|
|
</div>
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "KPI Type" %}</div>
|
|
<div class="value">{{ report.kpi_type }}</div>
|
|
</div>
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Risk Level" %}</div>
|
|
<div class="value">{{ report.risk_level }}</div>
|
|
</div>
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Dimension" %}</div>
|
|
<div class="value">{{ report.dimension }}</div>
|
|
</div>
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Data Collection" %}</div>
|
|
<div class="value">{{ report.data_collection_method }}</div>
|
|
</div>
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Frequency" %}</div>
|
|
<div class="value">{{ report.reporting_frequency }}</div>
|
|
</div>
|
|
{% if report.collector_name %}
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Collector" %}</div>
|
|
<div class="value">{{ report.collector_name }}</div>
|
|
</div>
|
|
{% endif %}
|
|
{% if report.analyzer_name %}
|
|
<div class="metadata-item">
|
|
<div class="label">{% trans "Analyzer" %}</div>
|
|
<div class="value">{{ report.analyzer_name }}</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Charts -->
|
|
{% if trend_chart or source_chart %}
|
|
<div class="charts-row">
|
|
{% if trend_chart %}
|
|
<div class="chart-box">
|
|
<img src="data:image/png;base64,{{ trend_chart }}" alt="Monthly Performance Trend" />
|
|
</div>
|
|
{% endif %}
|
|
{% if source_chart %}
|
|
<div class="chart-box" style="max-width: 520px;">
|
|
<img src="data:image/png;base64,{{ source_chart }}" alt="Complaints by Source" />
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Department Breakdown -->
|
|
{% if department_breakdowns %}
|
|
<div class="section-title">{% trans "Department Breakdown" %}</div>
|
|
<div class="breakdown-grid">
|
|
{% for dept in department_breakdowns %}
|
|
<div class="breakdown-card">
|
|
<h4>{{ dept.get_department_category_display }}</h4>
|
|
<div class="stat"><strong>{% trans "Complaints" %}:</strong> {{ dept.complaint_count }}</div>
|
|
<div class="stat"><strong>{% trans "Resolved" %}:</strong> {{ dept.resolved_count }}</div>
|
|
{% if dept.avg_resolution_days %}
|
|
<div class="stat"><strong>{% trans "Avg Resolution" %}:</strong> {{ dept.avg_resolution_days|floatformat:1 }} {% trans "days" %}</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Location Breakdown -->
|
|
{% if location_breakdowns %}
|
|
<div class="section-title">{% trans "Location Breakdown" %}</div>
|
|
<div class="breakdown-grid">
|
|
{% for loc in location_breakdowns %}
|
|
<div class="breakdown-card">
|
|
<h4>{{ loc.location_type }}</h4>
|
|
<div class="stat"><strong>{% trans "Complaints" %}:</strong> {{ loc.complaint_count }}</div>
|
|
<div class="stat"><strong>{% trans "Share" %}:</strong> {{ loc.percentage|floatformat:1 }}%</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- AI Analysis -->
|
|
{% if ai_analysis %}
|
|
<div class="page-break"></div>
|
|
<div class="section-title">{% trans "AI-Generated Analysis" %}</div>
|
|
|
|
{% if ai_analysis.executive_summary %}
|
|
<div class="ai-section">
|
|
<h4>{% trans "Executive Summary" %}</h4>
|
|
<p>{{ ai_analysis.executive_summary }}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ai_analysis.performance_analysis %}
|
|
<div class="ai-section">
|
|
<h4>{% trans "Performance Analysis" %}</h4>
|
|
<p>{{ ai_analysis.performance_analysis }}</p>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ai_analysis.key_findings %}
|
|
<div class="ai-section">
|
|
<h4>{% trans "Key Findings" %}</h4>
|
|
{% for finding in ai_analysis.key_findings %}
|
|
<div class="findings-box">{{ finding }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ai_analysis.reasons_for_delays %}
|
|
<div class="ai-section">
|
|
<h4>{% trans "Reasons for Delays" %}</h4>
|
|
<ul>
|
|
{% for reason in ai_analysis.reasons_for_delays %}
|
|
<li>{{ reason }}</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ai_analysis.recommendations %}
|
|
<div class="ai-section">
|
|
<h4>{% trans "Recommendations" %}</h4>
|
|
{% for rec in ai_analysis.recommendations %}
|
|
<div class="recommendation-box">{{ rec }}</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if ai_analysis.comparison_to_target %}
|
|
<div class="ai-section">
|
|
<h4>{% trans "Comparison to Target" %}</h4>
|
|
<p>{{ ai_analysis.comparison_to_target }}</p>
|
|
</div>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
<!-- Sign-off Section -->
|
|
<div class="signoff-grid">
|
|
<div class="signoff-box">
|
|
<div class="role">{% trans "Prepared By" %}</div>
|
|
<div class="line"></div>
|
|
<div class="line-label">{% trans "Name & Signature" %}</div>
|
|
</div>
|
|
<div class="signoff-box">
|
|
<div class="role">{% trans "Reviewed By" %}</div>
|
|
<div class="line"></div>
|
|
<div class="line-label">{% trans "Name & Signature" %}</div>
|
|
</div>
|
|
<div class="signoff-box">
|
|
<div class="role">{% trans "Approved By" %}</div>
|
|
<div class="line"></div>
|
|
<div class="line-label">{% trans "Name & Signature" %}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="report-footer">
|
|
PX360 — Patient Experience Management System |
|
|
{{ report.hospital.name }} |
|
|
{% trans "Report ID" %}: {{ report.id }} |
|
|
{% trans "Generated" %}: {{ generated_at }}
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|