v0.13.2 - s4 event pipeline complete #56
@@ -5,6 +5,29 @@ All notable changes to Terra-View will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.13.1] - 2026-05-29
|
||||||
|
|
||||||
|
Same-day patch on top of v0.13.0. Fixes the mic-chart unit default — v0.13.0 shipped with `dBL` as the default, but the PDF report renders the mic axis in psi, so the website chart and the printed report didn't match. Operator caught it within an hour of rollout. Also relabels the modal's "Captured at" field to "Time received" so it isn't mistaken for the device's trigger time.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Event-detail modal: mic chart now defaults to psi**, matching the PDF report's mic axis. The waveform/histogram chart's mic channel now renders in raw psi by default; operators who specifically prefer dB(L) on charts can flip it via Settings → General → "Event Report — Mic Channel Units". Peaks everywhere else (table tiles, modal Peaks section, KPI summaries) stay in dB(L) as before — this is strictly a chart-axis change.
|
||||||
|
- **Modal label: "Captured at" → "Time received"** (+ tooltip clarifying it's the SFM ingestion time, not the unit-local trigger time at the top of the modal). Same change in seismo-relay's standalone webapp for consistency.
|
||||||
|
|
||||||
|
### Migration Notes
|
||||||
|
|
||||||
|
The bundled `backend/migrate_add_mic_unit_pref.py` is now idempotent across both the v0.13.0 "add column" path and the v0.13.0 → v0.13.1 default flip. Existing rows sitting at the original `'dBL'` default (i.e. nobody touched the setting yet — true for almost everyone) get bumped to `'psi'` on migration.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/serversdown/terra-view
|
||||||
|
docker compose build terra-view && docker compose up -d terra-view
|
||||||
|
docker exec terra-view-terra-view-1 python3 /app/backend/migrate_add_mic_unit_pref.py
|
||||||
|
```
|
||||||
|
|
||||||
|
If you _did_ deliberately set the chart to dB(L) via Settings between v0.13.0 rollout and this patch, the migration will reset it — one click in Settings to restore. Trade-off considered acceptable given the very small user base and the freshness of the v0.13.0 release.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.13.0] - 2026-05-29
|
## [0.13.0] - 2026-05-29
|
||||||
|
|
||||||
The "SFM integration Phase 1" release. Closes the gap between Terra-View and the standalone SFM webapp on port 8200 — operators no longer need to bounce between the two for routine event review. The shared event-detail modal (used on `/sfm`, `/unit/{id}`, `/admin/events`, and `/projects/{p}/nrl/{l}`) gains a Chart.js waveform/histogram chart, inline PDF preview, original `.TXT` download, and a review form with false-trigger flag + reviewer + notes. `/admin/events` finally gets the modal too. A new Settings field controls the mic chart's display unit.
|
The "SFM integration Phase 1" release. Closes the gap between Terra-View and the standalone SFM webapp on port 8200 — operators no longer need to bounce between the two for routine event review. The shared event-detail modal (used on `/sfm`, `/unit/{id}`, `/admin/events`, and `/projects/{p}/nrl/{l}`) gains a Chart.js waveform/histogram chart, inline PDF preview, original `.TXT` download, and a review form with false-trigger flag + reviewer + notes. `/admin/events` finally gets the modal too. A new Settings field controls the mic chart's display unit.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Terra-View v0.13.0
|
# Terra-View v0.13.1
|
||||||
Backend API and HTMX-powered web interface for managing a mixed fleet of seismographs and field modems. Track deployments, monitor health in real time, merge roster intent with incoming telemetry, and control your fleet through a unified database and dashboard.
|
Backend API and HTMX-powered web interface for managing a mixed fleet of seismographs and field modems. Track deployments, monitor health in real time, merge roster intent with incoming telemetry, and control your fleet through a unified database and dashboard.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ Base.metadata.create_all(bind=engine)
|
|||||||
ENVIRONMENT = os.getenv("ENVIRONMENT", "production")
|
ENVIRONMENT = os.getenv("ENVIRONMENT", "production")
|
||||||
|
|
||||||
# Initialize FastAPI app
|
# Initialize FastAPI app
|
||||||
VERSION = "0.13.0"
|
VERSION = "0.13.1"
|
||||||
if ENVIRONMENT == "development":
|
if ENVIRONMENT == "development":
|
||||||
_build = os.getenv("BUILD_NUMBER", "0")
|
_build = os.getenv("BUILD_NUMBER", "0")
|
||||||
if _build and _build != "0":
|
if _build and _build != "0":
|
||||||
|
|||||||
@@ -3,10 +3,19 @@
|
|||||||
Database migration: Add mic_unit_pref column to user_preferences.
|
Database migration: Add mic_unit_pref column to user_preferences.
|
||||||
|
|
||||||
Adds a single field controlling the mic channel's unit on the event-
|
Adds a single field controlling the mic channel's unit on the event-
|
||||||
report waveform chart in the SFM event detail modal. "dBL" (default)
|
report waveform chart in the SFM event detail modal. "psi" (default —
|
||||||
or "psi". Peaks and KPI tiles elsewhere are always dBL regardless.
|
matches the PDF report's mic axis) or "dBL". Peaks and KPI tiles
|
||||||
|
elsewhere are always dBL regardless.
|
||||||
|
|
||||||
Idempotent — safe to re-run.
|
History: v0.13.0 originally shipped this with default "dBL", which
|
||||||
|
made the website chart inconsistent with the PDF. v0.13.1 flips the
|
||||||
|
default to "psi" so they match. This migration is idempotent and
|
||||||
|
covers three cases:
|
||||||
|
|
||||||
|
1. Fresh DB without the column — adds it with default 'psi'.
|
||||||
|
2. DB upgraded from v0.13.0 (column exists, value 'dBL') — flips to
|
||||||
|
'psi' on the assumption no operator deliberately picked 'dBL' yet.
|
||||||
|
3. DB upgraded from later — flip step is a no-op for non-'dBL' values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
@@ -32,24 +41,33 @@ def migrate():
|
|||||||
cur.execute("PRAGMA table_info(user_preferences)")
|
cur.execute("PRAGMA table_info(user_preferences)")
|
||||||
existing = {row[1] for row in cur.fetchall()}
|
existing = {row[1] for row in cur.fetchall()}
|
||||||
|
|
||||||
if "mic_unit_pref" in existing:
|
if "mic_unit_pref" not in existing:
|
||||||
print("mic_unit_pref already exists — nothing to do.")
|
|
||||||
conn.close()
|
|
||||||
return
|
|
||||||
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"ALTER TABLE user_preferences "
|
"ALTER TABLE user_preferences "
|
||||||
"ADD COLUMN mic_unit_pref TEXT DEFAULT 'dBL'"
|
"ADD COLUMN mic_unit_pref TEXT DEFAULT 'psi'"
|
||||||
)
|
)
|
||||||
# Backfill the single row that should exist (id=1) to the default,
|
# Backfill any rows where the column ended up NULL.
|
||||||
# in case the column ends up NULL on existing rows.
|
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"UPDATE user_preferences SET mic_unit_pref = 'dBL' "
|
"UPDATE user_preferences SET mic_unit_pref = 'psi' "
|
||||||
"WHERE mic_unit_pref IS NULL"
|
"WHERE mic_unit_pref IS NULL"
|
||||||
)
|
)
|
||||||
|
print("Added mic_unit_pref column (default 'psi').")
|
||||||
|
else:
|
||||||
|
print("mic_unit_pref column already exists.")
|
||||||
|
|
||||||
|
# v0.13.0 → v0.13.1 default-flip: rows still sitting at the original
|
||||||
|
# 'dBL' default get bumped to 'psi'. If any operator deliberately
|
||||||
|
# chose 'dBL' through Settings before this migration runs they'd
|
||||||
|
# get reset — acceptable trade-off given the small user base and
|
||||||
|
# the fact the setting is one click to restore.
|
||||||
|
cur.execute("UPDATE user_preferences SET mic_unit_pref = 'psi' "
|
||||||
|
"WHERE mic_unit_pref = 'dBL'")
|
||||||
|
flipped = cur.rowcount
|
||||||
|
if flipped:
|
||||||
|
print(f"Flipped {flipped} row(s) from 'dBL' to 'psi' (v0.13.0 default).")
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
print("Added mic_unit_pref to user_preferences (default 'dBL').")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+4
-2
@@ -136,8 +136,10 @@ class UserPreferences(Base):
|
|||||||
status_ok_threshold_hours = Column(Integer, default=12)
|
status_ok_threshold_hours = Column(Integer, default=12)
|
||||||
status_pending_threshold_hours = Column(Integer, default=24)
|
status_pending_threshold_hours = Column(Integer, default=24)
|
||||||
# Mic display units on the event-report waveform chart only — peaks
|
# Mic display units on the event-report waveform chart only — peaks
|
||||||
# and KPI tiles elsewhere are always dBL. "dBL" (default) or "psi".
|
# and KPI tiles elsewhere are always dBL. "psi" (default — matches
|
||||||
mic_unit_pref = Column(String, default="dBL")
|
# the PDF report) or "dBL". Default flipped in v0.13.1 after
|
||||||
|
# operator feedback that the chart should mirror the PDF.
|
||||||
|
mic_unit_pref = Column(String, default="psi")
|
||||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -294,7 +294,7 @@ def get_preferences(db: Session = Depends(get_db)):
|
|||||||
"calibration_warning_days": prefs.calibration_warning_days,
|
"calibration_warning_days": prefs.calibration_warning_days,
|
||||||
"status_ok_threshold_hours": prefs.status_ok_threshold_hours,
|
"status_ok_threshold_hours": prefs.status_ok_threshold_hours,
|
||||||
"status_pending_threshold_hours": prefs.status_pending_threshold_hours,
|
"status_pending_threshold_hours": prefs.status_pending_threshold_hours,
|
||||||
"mic_unit_pref": prefs.mic_unit_pref or "dBL",
|
"mic_unit_pref": prefs.mic_unit_pref or "psi",
|
||||||
"updated_at": prefs.updated_at.isoformat() if prefs.updated_at else None
|
"updated_at": prefs.updated_at.isoformat() if prefs.updated_at else None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ def update_preferences(
|
|||||||
"calibration_warning_days": prefs.calibration_warning_days,
|
"calibration_warning_days": prefs.calibration_warning_days,
|
||||||
"status_ok_threshold_hours": prefs.status_ok_threshold_hours,
|
"status_ok_threshold_hours": prefs.status_ok_threshold_hours,
|
||||||
"status_pending_threshold_hours": prefs.status_pending_threshold_hours,
|
"status_pending_threshold_hours": prefs.status_pending_threshold_hours,
|
||||||
"mic_unit_pref": prefs.mic_unit_pref or "dBL",
|
"mic_unit_pref": prefs.mic_unit_pref or "psi",
|
||||||
"updated_at": prefs.updated_at.isoformat() if prefs.updated_at else None
|
"updated_at": prefs.updated_at.isoformat() if prefs.updated_at else None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
const MIC_DBL_FLOOR = 60;
|
const MIC_DBL_FLOOR = 60;
|
||||||
|
|
||||||
let _charts = {}; // ch → Chart instance
|
let _charts = {}; // ch → Chart instance
|
||||||
let _micUnitPref = 'dBL'; // refreshed via fetch on first chart render
|
let _micUnitPref = 'psi'; // refreshed via fetch on first chart render
|
||||||
let _micUnitPrefLoaded = false; // one-shot fetch guard
|
let _micUnitPrefLoaded = false; // one-shot fetch guard
|
||||||
|
|
||||||
function _esc(s) {
|
function _esc(s) {
|
||||||
@@ -294,10 +294,10 @@
|
|||||||
const r = await fetch('/api/settings/preferences');
|
const r = await fetch('/api/settings/preferences');
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
const prefs = await r.json();
|
const prefs = await r.json();
|
||||||
_micUnitPref = prefs.mic_unit_pref === 'psi' ? 'psi' : 'dBL';
|
_micUnitPref = prefs.mic_unit_pref === 'dBL' ? 'dBL' : 'psi';
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Network error → silent fall back to default 'dBL'.
|
// Network error → silent fall back to default 'psi'.
|
||||||
}
|
}
|
||||||
_micUnitPrefLoaded = true;
|
_micUnitPrefLoaded = true;
|
||||||
return _micUnitPref;
|
return _micUnitPref;
|
||||||
|
|||||||
@@ -130,8 +130,8 @@
|
|||||||
</label>
|
</label>
|
||||||
<select id="mic-unit-pref"
|
<select id="mic-unit-pref"
|
||||||
class="w-full max-w-md px-4 py-2 text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-seismo-orange">
|
class="w-full max-w-md px-4 py-2 text-gray-900 dark:text-gray-100 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-seismo-orange">
|
||||||
<option value="dBL" selected>dB(L) — sound pressure level</option>
|
<option value="psi" selected>psi — raw pressure (matches PDF report)</option>
|
||||||
<option value="psi">psi — raw pressure</option>
|
<option value="dBL">dB(L) — sound pressure level</option>
|
||||||
</select>
|
</select>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
Applies only to the waveform chart inside the event detail modal. Peak values everywhere else (tables, KPIs, modal summary) stay in dB(L) regardless.
|
Applies only to the waveform chart inside the event detail modal. Peak values everywhere else (tables, KPIs, modal summary) stay in dB(L) regardless.
|
||||||
@@ -787,7 +787,7 @@ async function loadPreferences() {
|
|||||||
document.getElementById('refresh-interval').value = prefs.auto_refresh_interval || 10;
|
document.getElementById('refresh-interval').value = prefs.auto_refresh_interval || 10;
|
||||||
|
|
||||||
// Load event-report mic units
|
// Load event-report mic units
|
||||||
document.getElementById('mic-unit-pref').value = prefs.mic_unit_pref || 'dBL';
|
document.getElementById('mic-unit-pref').value = prefs.mic_unit_pref || 'psi';
|
||||||
|
|
||||||
// Load status thresholds
|
// Load status thresholds
|
||||||
document.getElementById('ok-threshold').value = prefs.status_ok_threshold_hours || 12;
|
document.getElementById('ok-threshold').value = prefs.status_ok_threshold_hours || 12;
|
||||||
|
|||||||
Reference in New Issue
Block a user