release: v0.13.1 — mic chart defaults to psi (matches PDF)

v0.13.0 shipped the mic_unit_pref default as "dBL", which made the
website chart's mic axis inconsistent with the PDF report (which
renders psi).  Original brief was always "psi on charts, dBL on
peaks" — I implemented the default backwards.  Operator caught it
within an hour of rollout.

Same-day patch:
- backend/models.py: default "dBL" → "psi"
- migrate_add_mic_unit_pref.py: idempotent across both fresh DB
  ("add column with psi default") and v0.13.0 upgrade ("flip dBL
  rows to psi").  One-row table, freshness assumed.
- backend/routers/settings.py: GET/PUT fallback "dBL" → "psi"
- templates/settings.html: dropdown's `selected` flag moves to psi
  + reorders options + relabels with "(matches PDF report)" hint
- backend/static/event-modal.js: module-level fallback + branch
  conditions flip to make psi the unset/error default

Includes the "Captured at" → "Time received" relabel from earlier
in the day (already-shipped commit 43c804d) rolled into the
release notes.

Migration is idempotent + safe to re-run; rolled out on the dev
container during this commit's smoke test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 19:57:32 +00:00
parent 43c804d0c4
commit 5ed00bf70e
8 changed files with 73 additions and 30 deletions
+36 -18
View File
@@ -3,10 +3,19 @@
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.
report waveform chart in the SFM event detail modal. "psi" (default
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
@@ -32,24 +41,33 @@ def migrate():
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
if "mic_unit_pref" not in existing:
cur.execute(
"ALTER TABLE user_preferences "
"ADD COLUMN mic_unit_pref TEXT DEFAULT 'psi'"
)
# Backfill any rows where the column ended up NULL.
cur.execute(
"UPDATE user_preferences SET mic_unit_pref = 'psi' "
"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).")
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__":