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:
+27
-1
@@ -46,7 +46,7 @@ from typing import Optional
|
||||
|
||||
# FastAPI / Pydantic
|
||||
try:
|
||||
from fastapi import Body, FastAPI, File, HTTPException, Query, UploadFile
|
||||
from fastapi import Body, FastAPI, File, HTTPException, Query, Response, UploadFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
|
||||
from pydantic import BaseModel
|
||||
@@ -2178,6 +2178,32 @@ def db_event_blastware_file(event_id: str) -> FileResponse:
|
||||
)
|
||||
|
||||
|
||||
@app.get("/db/events/{event_id}/report.pdf")
|
||||
def db_event_report_pdf(event_id: str):
|
||||
"""Render an Instantel-style Event Report as a PDF.
|
||||
|
||||
Single-page letter portrait, matches the BW Event Report's data
|
||||
coverage and layout (header / mic block / per-channel stats /
|
||||
waveform plot). V0.20.0 stub — exact visual being iterated
|
||||
against reference PDFs in ``docs/reference/instantel/``.
|
||||
|
||||
Returns 404 if the event is unknown or has no waveform data on
|
||||
disk (same condition as /waveform.json).
|
||||
"""
|
||||
from sfm import report_pdf
|
||||
rd = report_pdf.gather_report_data(_get_db(), _get_store(), event_id)
|
||||
if rd is None:
|
||||
raise HTTPException(status_code=404, detail=f"Event {event_id} not found or has no waveform")
|
||||
pdf_bytes = report_pdf.render_event_report_pdf(rd)
|
||||
# Suggested download filename based on the BW file basename.
|
||||
fname = (rd.file_name or event_id).replace(".", "_")
|
||||
return Response(
|
||||
content=pdf_bytes,
|
||||
media_type="application/pdf",
|
||||
headers={"Content-Disposition": f'inline; filename="{fname}_report.pdf"'},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/db/events/{event_id}/waveform.json")
|
||||
def db_event_waveform_json(event_id: str) -> dict:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user