From b2bfa6d268649c47616c6baf4350c2711222929d Mon Sep 17 00:00:00 2001 From: serversdown Date: Thu, 28 May 2026 05:41:26 +0000 Subject: [PATCH 1/9] compose: set TZ=America/New_York on terra-view + sfm services MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default display timezone for server logs + PDF report rendering on both terra-view and sfm services. Override per-deployment in this file for non-US-East installations. DB columns are always UTC regardless — only affects what operators see in logs / PDFs / any text-rendered timestamp. Modal display uses browser TZ via toLocaleString (no server config needed). Pairs with seismo-relay commit 6381dcb (tz env var support in the Dockerfile + report_pdf UTC→local conversion). Co-Authored-By: Claude Opus 4.7 (1M context) --- docker-compose.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index a9a1ca2..77f682e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,6 +11,10 @@ services: - ENVIRONMENT=production - SLMM_BASE_URL=http://host.docker.internal:8100 - SFM_BASE_URL=http://sfm:8200 + # Display timezone for server logs + any text-rendered timestamps. + # DB columns are stored UTC regardless; this only affects what + # operators see. Override here for non-US-East deployments. + - TZ=America/New_York restart: unless-stopped depends_on: - slmm @@ -59,6 +63,10 @@ services: environment: - PYTHONUNBUFFERED=1 - PORT=8200 + # Display timezone — affects server log timestamps, the PDF + # report renderer's UTC→local conversions, and matplotlib's + # datetime axes. DB columns (created_at etc.) are always UTC. + - TZ=America/New_York restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8200/health"] -- 2.52.0 From db8d666aa1d3150943f30f4158af16f711e10f54 Mon Sep 17 00:00:00 2001 From: serversdown Date: Fri, 29 May 2026 00:56:41 +0000 Subject: [PATCH 2/9] settings: add mic_unit_pref for event-report chart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New UserPreferences field controls the mic channel's unit on the SFM event-detail modal's waveform chart only. "dBL" default, "psi" alternate. Peaks everywhere else (tables, KPI tiles, modal summary) stay in dBL regardless — this is strictly a chart-axis preference. Surfaced as a single dropdown on Settings → General, below the auto-refresh interval. Setting up the storage half ahead of the chart port in the next commit, so the chart can read the value from /api/settings/preferences on first render instead of needing a follow-up wiring pass. Includes idempotent backend/migrate_add_mic_unit_pref.py for fleets already on an older schema. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/migrate_add_mic_unit_pref.py | 56 ++++++++++++++++++++++++++++ backend/models.py | 3 ++ backend/routers/settings.py | 3 ++ templates/settings.html | 22 ++++++++++- 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 backend/migrate_add_mic_unit_pref.py diff --git a/backend/migrate_add_mic_unit_pref.py b/backend/migrate_add_mic_unit_pref.py new file mode 100644 index 0000000..b471949 --- /dev/null +++ b/backend/migrate_add_mic_unit_pref.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +""" +Database migration: Add mic_unit_pref column to user_preferences. + +Adds a single field controlling the mic channel's unit on the event- +report waveform chart in the SFM event detail modal. "dBL" (default) +or "psi". Peaks and KPI tiles elsewhere are always dBL regardless. + +Idempotent — safe to re-run. +""" + +import sqlite3 +from pathlib import Path + + +def migrate(): + possible_paths = [ + Path("data/seismo_fleet.db"), + Path("data/sfm.db"), + Path("data/seismo.db"), + ] + db_path = next((p for p in possible_paths if p.exists()), None) + if db_path is None: + print(f"Database not found in any of: {[str(p) for p in possible_paths]}") + print("Will be created with the new column when models.py initialises.") + return + + print(f"Using database: {db_path}") + conn = sqlite3.connect(db_path) + cur = conn.cursor() + + cur.execute("PRAGMA table_info(user_preferences)") + existing = {row[1] for row in cur.fetchall()} + + if "mic_unit_pref" in existing: + print("mic_unit_pref already exists — nothing to do.") + conn.close() + return + + cur.execute( + "ALTER TABLE user_preferences " + "ADD COLUMN mic_unit_pref TEXT DEFAULT 'dBL'" + ) + # Backfill the single row that should exist (id=1) to the default, + # in case the column ends up NULL on existing rows. + cur.execute( + "UPDATE user_preferences SET mic_unit_pref = 'dBL' " + "WHERE mic_unit_pref IS NULL" + ) + conn.commit() + conn.close() + print("Added mic_unit_pref to user_preferences (default 'dBL').") + + +if __name__ == "__main__": + migrate() diff --git a/backend/models.py b/backend/models.py index 3ba1e11..5be50ea 100644 --- a/backend/models.py +++ b/backend/models.py @@ -135,6 +135,9 @@ class UserPreferences(Base): calibration_warning_days = Column(Integer, default=30) status_ok_threshold_hours = Column(Integer, default=12) status_pending_threshold_hours = Column(Integer, default=24) + # Mic display units on the event-report waveform chart only — peaks + # and KPI tiles elsewhere are always dBL. "dBL" (default) or "psi". + mic_unit_pref = Column(String, default="dBL") updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) diff --git a/backend/routers/settings.py b/backend/routers/settings.py index e32f4d6..3b01e70 100644 --- a/backend/routers/settings.py +++ b/backend/routers/settings.py @@ -267,6 +267,7 @@ class PreferencesUpdate(BaseModel): calibration_warning_days: Optional[int] = None status_ok_threshold_hours: Optional[int] = None status_pending_threshold_hours: Optional[int] = None + mic_unit_pref: Optional[str] = None @router.get("/preferences") @@ -293,6 +294,7 @@ def get_preferences(db: Session = Depends(get_db)): "calibration_warning_days": prefs.calibration_warning_days, "status_ok_threshold_hours": prefs.status_ok_threshold_hours, "status_pending_threshold_hours": prefs.status_pending_threshold_hours, + "mic_unit_pref": prefs.mic_unit_pref or "dBL", "updated_at": prefs.updated_at.isoformat() if prefs.updated_at else None } @@ -334,6 +336,7 @@ def update_preferences( "calibration_warning_days": prefs.calibration_warning_days, "status_ok_threshold_hours": prefs.status_ok_threshold_hours, "status_pending_threshold_hours": prefs.status_pending_threshold_hours, + "mic_unit_pref": prefs.mic_unit_pref or "dBL", "updated_at": prefs.updated_at.isoformat() if prefs.updated_at else None } diff --git a/templates/settings.html b/templates/settings.html index ba5532e..662a70e 100644 --- a/templates/settings.html +++ b/templates/settings.html @@ -122,6 +122,21 @@ How often the dashboard should refresh automatically

+ + +
+ + +

+ Applies only to the waveform chart inside the event detail modal. Peak values everywhere else (tables, KPIs, modal summary) stay in dB(L) regardless. +

+
+ + `; + } + // ── Waveform / histogram chart helpers ────────────────────────── async function _loadMicUnitPref() { @@ -527,27 +568,47 @@ const src = s.source || {}; const sizeKb = bw.filesize ? (bw.filesize / 1024).toFixed(1) : null; const canDownloadBinary = !!(bw.available && bw.filename && eventId); + const txtFilename = src && src.txt_filename; + const reportPdfUrl = `/api/sfm/db/events/${encodeURIComponent(eventId)}/report.pdf`; + const reportTxtUrl = `/api/sfm/db/events/${encodeURIComponent(eventId)}/ascii_report.txt`; const downloadButtons = ` +