sfm: Event Report PDF generation (v0.20.0 stub layout)
New endpoint GET /db/events/{id}/report.pdf returns a single-page
letter-portrait PDF for any event with waveform data on disk.
Architecture:
sfm/report_pdf.py — gather_report_data() assembles fields from
SeismoDb row + .sfm.json sidecar (bw_report block) + .h5 samples;
render_event_report_pdf() turns that into PDF bytes via matplotlib.
sfm/server.py — new endpoint wires them together, streams PDF back
with Content-Disposition: inline so the browser displays it.
sfm_webapp.html — new "Download PDF" button in the event modal
footer that opens the endpoint in a new tab.
Fields surfaced — same coverage as a Blastware Event Report:
Header metadata (date/time, trigger source, range, sample rate,
project, client, operator, location, serial+firmware,
battery, calibration, file name)
Microphone block (PSPL in dB(L) + psi, ZC freq, channel test)
Per-channel stats (PPV, ZC Freq, Time of Peak, Peak Accel,
Peak Disp, Sensor Check) for Tran/Vert/Long
Peak Vector Sum
Waveform plot (MicL/Long/Vert/Tran stacked, shared time axis,
trigger marker, symmetric Y for geo, zero-anchored
mic) — OR per-interval bar chart for histograms.
Rendering pipeline = matplotlib only (vector PDF, no headless-browser
dep). Adds matplotlib>=3.8 to deps.
Visual layout is approximate until reference PDFs from Instantel land
at docs/reference/instantel/ for iteration. USBM RI8507 / OSMRE
compliance chart is stubbed (placeholder rectangle) — separate work
item.
Smoke-tested on a K558 waveform event: 77 KB valid PDF, all fields
populated correctly from the snapshot DB.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2892,6 +2892,18 @@ function closeSidecarModal() {
|
||||
_destroyScCharts();
|
||||
}
|
||||
|
||||
// Trigger a PDF download for the currently-open event. The browser
|
||||
// handles the actual save dialog from the Content-Disposition header
|
||||
// the server sends.
|
||||
function downloadEventReport() {
|
||||
if (!_scCurrentEventId) return;
|
||||
const url = `${api()}/db/events/${_scCurrentEventId}/report.pdf`;
|
||||
// Open in a new tab — browser prompts to save or displays inline,
|
||||
// and a failed fetch (e.g. 404 for events with no waveform) shows
|
||||
// its JSON error in-page rather than silently failing.
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
|
||||
function onSidecarOverlayClick(e) {
|
||||
// Click on the dimmed backdrop (but NOT on the modal itself) closes.
|
||||
if (e.target.id === 'sc-overlay') closeSidecarModal();
|
||||
@@ -3193,6 +3205,10 @@ if (currentSection === 'db') {
|
||||
</div>
|
||||
<div class="sc-footer">
|
||||
<span class="sc-status" id="sc-status"></span>
|
||||
<button class="btn btn-ghost" id="sc-pdf-btn" onclick="downloadEventReport()"
|
||||
title="Download an Instantel-style Event Report PDF for this event">
|
||||
Download PDF
|
||||
</button>
|
||||
<button class="btn btn-ghost" onclick="closeSidecarModal()">Cancel</button>
|
||||
<button class="btn" id="sc-save-btn" onclick="saveSidecarReview()">Save</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user