settings: add mic_unit_pref for event-report chart
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) <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+21
-1
@@ -122,6 +122,21 @@
|
||||
How often the dashboard should refresh automatically
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Event-Report Mic Units -->
|
||||
<div>
|
||||
<label for="mic-unit-pref" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Event Report — Mic Channel Units
|
||||
</label>
|
||||
<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">
|
||||
<option value="dBL" selected>dB(L) — sound pressure level</option>
|
||||
<option value="psi">psi — raw pressure</option>
|
||||
</select>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button onclick="saveGeneralSettings()" class="mt-6 px-6 py-3 bg-seismo-orange hover:bg-orange-600 text-white rounded-lg transition-colors">
|
||||
@@ -771,6 +786,9 @@ async function loadPreferences() {
|
||||
// Load auto-refresh interval
|
||||
document.getElementById('refresh-interval').value = prefs.auto_refresh_interval || 10;
|
||||
|
||||
// Load event-report mic units
|
||||
document.getElementById('mic-unit-pref').value = prefs.mic_unit_pref || 'dBL';
|
||||
|
||||
// Load status thresholds
|
||||
document.getElementById('ok-threshold').value = prefs.status_ok_threshold_hours || 12;
|
||||
document.getElementById('pending-threshold').value = prefs.status_pending_threshold_hours || 24;
|
||||
@@ -788,6 +806,7 @@ async function saveGeneralSettings() {
|
||||
const timezone = document.getElementById('timezone-select').value;
|
||||
const theme = document.querySelector('input[name="theme"]:checked').value;
|
||||
const autoRefreshInterval = parseInt(document.getElementById('refresh-interval').value);
|
||||
const micUnitPref = document.getElementById('mic-unit-pref').value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings/preferences', {
|
||||
@@ -796,7 +815,8 @@ async function saveGeneralSettings() {
|
||||
body: JSON.stringify({
|
||||
timezone,
|
||||
theme,
|
||||
auto_refresh_interval: autoRefreshInterval
|
||||
auto_refresh_interval: autoRefreshInterval,
|
||||
mic_unit_pref: micUnitPref
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user