Feat: add complete combined sound report creation tool (wizard), add new slm schema for each model feat: update project header link for combined report wizard feat: add migration script to backfill device_model in monitoring_sessions feat: implement combined report preview template with spreadsheet functionality feat: create combined report wizard template for report generation.
128 lines
4.4 KiB
Python
128 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Migration: Add device_model column to monitoring_sessions table.
|
|
|
|
Records which physical SLM model produced each session's data (e.g. "NL-43",
|
|
"NL-53", "NL-32"). Used by report generation to apply the correct parsing
|
|
logic without re-opening files to detect format.
|
|
|
|
Run once inside the Docker container:
|
|
docker exec terra-view python3 backend/migrate_add_session_device_model.py
|
|
|
|
Backfill strategy for existing rows:
|
|
1. If session.unit_id is set, use roster.slm_model for that unit.
|
|
2. Else, peek at the first .rnd file in the session: presence of the 'LAeq'
|
|
column header identifies AU2 / NL-32 format.
|
|
Sessions where neither hint is available remain NULL — the file-content
|
|
fallback in report code handles them transparently.
|
|
"""
|
|
import csv
|
|
import io
|
|
from pathlib import Path
|
|
|
|
DB_PATH = Path("data/seismo_fleet.db")
|
|
|
|
|
|
def _peek_first_row(abs_path: Path) -> dict:
|
|
"""Read only the header + first data row of an RND file. Very cheap."""
|
|
try:
|
|
with open(abs_path, "r", encoding="utf-8", errors="replace") as f:
|
|
reader = csv.DictReader(f)
|
|
return next(reader, None) or {}
|
|
except Exception:
|
|
return {}
|
|
|
|
|
|
def _detect_model_from_rnd(abs_path: Path) -> str | None:
|
|
"""Return 'NL-32' if file uses AU2 column format, else None."""
|
|
row = _peek_first_row(abs_path)
|
|
if "LAeq" in row:
|
|
return "NL-32"
|
|
return None
|
|
|
|
|
|
def migrate():
|
|
import sqlite3
|
|
|
|
if not DB_PATH.exists():
|
|
print(f"Database not found at {DB_PATH}. Are you running from /home/serversdown/terra-view?")
|
|
return
|
|
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.row_factory = sqlite3.Row
|
|
cur = conn.cursor()
|
|
|
|
# ── 1. Add column (idempotent) ───────────────────────────────────────────
|
|
cur.execute("PRAGMA table_info(monitoring_sessions)")
|
|
existing_cols = {row["name"] for row in cur.fetchall()}
|
|
|
|
if "device_model" not in existing_cols:
|
|
cur.execute("ALTER TABLE monitoring_sessions ADD COLUMN device_model TEXT")
|
|
conn.commit()
|
|
print("✓ Added column device_model to monitoring_sessions")
|
|
else:
|
|
print("○ Column device_model already exists — skipping ALTER TABLE")
|
|
|
|
# ── 2. Backfill existing NULL rows ───────────────────────────────────────
|
|
cur.execute(
|
|
"SELECT id, unit_id FROM monitoring_sessions WHERE device_model IS NULL"
|
|
)
|
|
sessions = cur.fetchall()
|
|
print(f"Backfilling {len(sessions)} session(s) with device_model=NULL...")
|
|
|
|
updated = skipped = 0
|
|
for row in sessions:
|
|
session_id = row["id"]
|
|
unit_id = row["unit_id"]
|
|
device_model = None
|
|
|
|
# Strategy A: look up unit's slm_model from the roster
|
|
if unit_id:
|
|
cur.execute(
|
|
"SELECT slm_model FROM roster WHERE id = ?", (unit_id,)
|
|
)
|
|
unit_row = cur.fetchone()
|
|
if unit_row and unit_row["slm_model"]:
|
|
device_model = unit_row["slm_model"]
|
|
|
|
# Strategy B: detect from first .rnd file in the session
|
|
if device_model is None:
|
|
cur.execute(
|
|
"""SELECT file_path FROM data_files
|
|
WHERE session_id = ?
|
|
AND lower(file_path) LIKE '%.rnd'
|
|
LIMIT 1""",
|
|
(session_id,),
|
|
)
|
|
file_row = cur.fetchone()
|
|
if file_row:
|
|
abs_path = Path("data") / file_row["file_path"]
|
|
device_model = _detect_model_from_rnd(abs_path)
|
|
# None here means NL-43/NL-53 format (or unreadable file) —
|
|
# leave as NULL so the existing fallback applies.
|
|
|
|
if device_model:
|
|
cur.execute(
|
|
"UPDATE monitoring_sessions SET device_model = ? WHERE id = ?",
|
|
(device_model, session_id),
|
|
)
|
|
updated += 1
|
|
else:
|
|
skipped += 1
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
print(f"✓ Backfilled {updated} session(s) with a device_model.")
|
|
if skipped:
|
|
print(
|
|
f" {skipped} session(s) left as NULL "
|
|
"(no unit link and no AU2 file hint — NL-43/NL-53 or unknown; "
|
|
"file-content detection applies at report time)."
|
|
)
|
|
print("Migration complete.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
migrate()
|