""" Migration: add metadata-backfill support. Adds: 1. `unit_assignments.source` column (TEXT, default 'manual'). Lets us audit which assignments were created by the metadata-backfill parser vs by a human, and bulk-undo parser actions if needed. 2. `metadata_backfill_decisions` table. Tracks operator decisions per cluster_id so the wizard remembers what's been skipped, what's been applied, and what's pending across re-scans. Idempotent — safe to re-run. Non-destructive — adds only. Run with: docker exec terra-view-terra-view-1 python3 /app/backend/migrate_add_metadata_backfill.py """ import os import sqlite3 DB_PATH = "./data/seismo_fleet.db" def migrate_database(): if not os.path.exists(DB_PATH): print(f"Database not found at {DB_PATH}") return print(f"Migrating database: {DB_PATH}") conn = sqlite3.connect(DB_PATH) cur = conn.cursor() # ── 1. unit_assignments.source column ────────────────────────────────── cur.execute("PRAGMA table_info(unit_assignments)") cols = {row[1] for row in cur.fetchall()} if "source" not in cols: print("Adding unit_assignments.source column (default 'manual') ...") cur.execute( "ALTER TABLE unit_assignments ADD COLUMN source TEXT DEFAULT 'manual'" ) # Backfill: any existing row gets source='manual' cur.execute("UPDATE unit_assignments SET source='manual' WHERE source IS NULL") conn.commit() print(" Done.") else: print("unit_assignments.source already exists — skipping") # ── 2. metadata_backfill_decisions table ────────────────────────────── cur.execute( "SELECT name FROM sqlite_master WHERE type='table' AND name='metadata_backfill_decisions'" ) if cur.fetchone() is None: print("Creating metadata_backfill_decisions table ...") cur.execute(""" CREATE TABLE metadata_backfill_decisions ( cluster_id TEXT PRIMARY KEY, -- deterministic hash status TEXT NOT NULL, -- pending | applied | skipped | conflict confidence TEXT NOT NULL, -- high | medium | low (at time of decision) decided_at TEXT, -- when applied/skipped decided_by TEXT, -- 'background' | 'operator' | 'auto-high' applied_assignment_id TEXT, -- FK to unit_assignments (if applied) notes TEXT, first_seen_at TEXT NOT NULL, last_seen_at TEXT NOT NULL, serial TEXT NOT NULL, project_raw TEXT, location_raw TEXT, first_event_ts TEXT, last_event_ts TEXT, event_count INTEGER NOT NULL DEFAULT 0 ) """) cur.execute( "CREATE INDEX idx_mbd_status ON metadata_backfill_decisions(status)" ) cur.execute( "CREATE INDEX idx_mbd_last_seen ON metadata_backfill_decisions(last_seen_at)" ) cur.execute( "CREATE INDEX idx_mbd_serial ON metadata_backfill_decisions(serial)" ) conn.commit() print(" Done.") else: print("metadata_backfill_decisions table already exists — skipping") conn.close() print("\nMigration complete.") if __name__ == "__main__": migrate_database()