feat: add waveform download and storage.

This commit is contained in:
2026-04-14 02:15:33 -04:00
parent b384ba66d1
commit edb4698bfb
5 changed files with 195 additions and 7 deletions
+42 -4
View File
@@ -81,6 +81,7 @@ CREATE TABLE IF NOT EXISTS events (
sample_rate INTEGER,
record_type TEXT, -- "single_shot" | "continuous"
false_trigger INTEGER NOT NULL DEFAULT 0, -- 0=no, 1=yes (manual flag)
waveform_blob TEXT, -- JSON waveform response (channels + metadata)
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
UNIQUE(serial, timestamp)
);
@@ -216,6 +217,17 @@ class SeismoDb:
""")
log.info("_migrate: monitor_log table rebuilt OK")
# Migration 3: add waveform_blob column to events (nullable TEXT).
# ALter TABLE ADD COLUMN is safe in SQLite for nullable columns — no rebuild needed.
col_names = {
row[1]
for row in conn.execute("PRAGMA table_info(events)").fetchall()
}
if "waveform_blob" not in col_names:
log.info("_migrate: adding waveform_blob column to events")
conn.execute("ALTER TABLE events ADD COLUMN waveform_blob TEXT")
log.info("_migrate: waveform_blob column added OK")
@staticmethod
def _iso(dt: Optional[datetime.datetime]) -> Optional[str]:
return dt.isoformat() if dt is not None else None
@@ -282,12 +294,19 @@ class SeismoDb:
*,
serial: str,
session_id: Optional[str] = None,
waveform_blobs: Optional[dict[str, str]] = None,
) -> tuple[int, int]:
"""
Insert triggered events. Silently skips duplicates (serial+timestamp).
Returns (inserted, skipped).
waveform_blobs: optional mapping of waveform_key (hex str) → JSON string
containing the full waveform response (channels + metadata). When provided,
the blob is stored alongside the event row and is retrievable via
GET /db/events/{id}/waveform.
"""
inserted = skipped = 0
blobs = waveform_blobs or {}
with self._connect() as conn:
for ev in events:
key = ev._waveform_key.hex() if ev._waveform_key else None
@@ -305,8 +324,9 @@ class SeismoDb:
except Exception:
ts = str(ev.timestamp)
pv = ev.peak_values
pi = ev.project_info
pv = ev.peak_values
pi = ev.project_info
blob = blobs.get(key)
try:
conn.execute(
@@ -315,8 +335,8 @@ class SeismoDb:
(id, serial, waveform_key, session_id, timestamp,
tran_ppv, vert_ppv, long_ppv, peak_vector_sum, mic_ppv,
project, client, operator, sensor_location,
sample_rate, record_type)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
sample_rate, record_type, waveform_blob)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
self._new_id(), serial, key, session_id, ts,
@@ -331,6 +351,7 @@ class SeismoDb:
pi.sensor_location if pi else None,
ev.sample_rate,
ev.record_type,
blob,
),
)
inserted += 1
@@ -387,6 +408,23 @@ class SeismoDb:
)
return cur.rowcount > 0
def get_event_waveform(self, event_id: str) -> tuple[bool, Optional[str]]:
"""
Return (found, waveform_blob) for a given event UUID.
found=False means the event row doesn't exist.
found=True, blob=None means the event exists but has no stored waveform
(e.g. downloaded before waveform storage was implemented).
found=True, blob=<str> means the full waveform JSON is available.
"""
with self._connect() as conn:
row = conn.execute(
"SELECT waveform_blob FROM events WHERE id = ?", (event_id,)
).fetchone()
if row is None:
return False, None
return True, row["waveform_blob"]
# ── Monitor log ───────────────────────────────────────────────────────────
def insert_monitor_log(