feat: FASE 6.5 - Report PDF (WeasyPrint + Kaleido)

Add PDF report generation for Metrologist dashboard with SPC and
measurement reports including SVG charts, capability indices, and
company logo embedding.

New files:
- server/services/report_service.py (Jinja2 + Plotly/Kaleido + WeasyPrint)
- server/routers/reports.py (2 GET endpoints with auth)
- server/templates/reports/ (base, spc, measurement HTML templates)

Modified:
- server/main.py (register reports router)
- client dashboard (download buttons + proxy routes)
- i18n strings IT/EN

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-07 15:24:32 +01:00
parent bcd807e57d
commit 26e5b9343d
10 changed files with 1056 additions and 3 deletions
@@ -0,0 +1,75 @@
{% extends "base_report.html" %}
{% block report_title %}Measurement Report{% endblock %}
{% block content %}
<!-- Recipe info + filters -->
<div class="info-box">
<span class="label">Recipe:</span> {{ recipe.code }} — {{ recipe.name }}
{% if filters_desc %}
<br>
<span class="label">Filters:</span> {{ filters_desc | join(' | ') }}
{% endif %}
</div>
<!-- Summary -->
<div class="section">
<div class="section-title">Summary</div>
<div class="summary-grid">
<div class="summary-card">
<div class="number mono">{{ summary.total }}</div>
<div class="label">Total</div>
</div>
<div class="summary-card pass-bg">
<div class="number mono pass">{{ summary.pass_count }}</div>
<div class="label">Pass ({{ summary.pass_rate }}%)</div>
</div>
<div class="summary-card warning-bg">
<div class="number mono warning">{{ summary.warning_count }}</div>
<div class="label">Warning ({{ summary.warning_rate }}%)</div>
</div>
<div class="summary-card fail-bg">
<div class="number mono fail">{{ summary.fail_count }}</div>
<div class="label">Fail ({{ summary.fail_rate }}%)</div>
</div>
</div>
</div>
<!-- Measurements Table -->
<div class="section">
<div class="section-title">Measurements ({{ n_measurements }})</div>
<table>
<thead>
<tr>
<th>#</th>
<th>Measurement Point</th>
<th>Value</th>
<th>Unit</th>
<th>Result</th>
<th>Date</th>
<th>Operator</th>
<th>Lot</th>
<th>Serial</th>
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr class="{% if row.pass_fail == 'fail' %}fail-bg{% elif row.pass_fail == 'warning' %}warning-bg{% endif %}">
<td class="mono">{{ row.num }}</td>
<td>{{ row.subtask }}</td>
<td class="mono">{{ row.value }}</td>
<td>{{ row.unit }}</td>
<td>
<span class="{{ row.pass_fail }}">{{ row.pass_fail | upper }}</span>
</td>
<td class="mono">{{ row.measured_at }}</td>
<td>{{ row.operator }}</td>
<td>{{ row.lot_number }}</td>
<td>{{ row.serial_number }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}