-
released this
2026-05-15 19:18:31 -04:00 | 30 commits to main since this release[0.11.0] - 2026-05-15
Operator-facing polish release. All work builds on the v0.10.0 SFM integration foundation — this release is about making the day-to-day workflows (managing locations, cleaning up bad attributions, browsing deployments) faster and less error-prone.
Added
- Soft-remove monitoring locations (
POST /api/projects/{p}/locations/{l}/remove+/restore): mark a location as no longer actively monitored without destroying historical events. Cascade-closes active unit assignments and cancels pending scheduled actions at the location. Restored locations rejoin the active list (assignments are NOT auto-reopened — operator creates new ones if resuming). Project page splits locations into Active and Removed sections; removed cards are greyed out, badged with the removal date + reason, and offer a Restore button. - Per-unit deployment Gantt chart above the existing Deployment Timeline list on every seismograph unit detail page. Plain-SVG rendering, color per location, today marker (orange dashed line), reduced-opacity bars for closed assignments, blue outlines on metadata-backfilled assignments, dashed blue underlines marking mergeable groups. Click a bar to scroll the matching list row into view with a flash highlight.
- Merge consecutive same-location assignments (
POST /api/projects/{p}/assignments/merge): operators often end up with several rows representing one continuous deployment (after remove/restore, or metadata-backfill adjacent to a manual record). Now auto-detected and surfaceable in the timeline header — one click combines them into a single record. Preserves the earliest record's notes + ingest source, writes anassignment_mergedaudit entry, deletes the others. - Delete assignment for mis-clicks (
DELETE /api/projects/{p}/assignments/{a}): hard-deletes a bogus assignment row that was never a real deployment. Trash icon in each row of the location's Deployment History panel. Refuses the delete if anyMonitoringSessionexists in the assignment's window — those should go through Unassign instead, which preserves audit history. Writes anassignment_deletedUnitHistory row. - Drag-to-reorder location cards: each active card has a six-dot drag handle on the left. Drag/drop reorders the DOM and persists via
POST /api/projects/{p}/locations/reorder. Implementation uses native HTML5 drag-and-drop (no library). New locations land at the end (sort_order = max + 1); removed locations stay sorted by removal date. - Three-dot kebab menu on location cards: replaces the four inline pill buttons (Unassign / Edit / Remove / Delete) with a single ⋮ menu. Click ⋮ to open; click outside or Escape to close; only one menu open at a time.
- Event count on vibration location cards: vibration cards now show "{N} events" sourced from SFM via concurrent fan-out, instead of "Sessions: 0" (sessions don't exist under the watcher-forward pipeline). Sound locations still show session counts.
- Project overview location map: right column of every project's overview replaces the lightly-used Upcoming Actions panel with a Leaflet map. One pin per active monitoring location (parsed from the
coordinatesfield). Click pin → scrolls + flashes the matching card. Tooltip on hover. Locations without coordinates surface as an inline hint below the map. If the project has pending scheduled actions, a small "{N} upcoming actions →" link appears in the card header that switches to the Schedules tab.
Changed
- Backfill location fuzzy matcher is now stricter:
rapidfuzz.WRatiowas over-confident on location names because their shared boilerplate vocabulary ("Area", "Loc", numbers) inflated scores. Example false positive that prompted the change:"Area 2 - Brookville Dam - Loc 2 East"vs"Area 1 - Loc 1 - 87 Jenks"scored 86% via WRatio. Now usestoken_set_ratioas the base scorer plus a 0.30 penalty when the two strings have disjoint multi-digit numeric tokens. Catches the "same project, different address number" case ("68 Jenks"vs"87 Jenks") that pure token-set scoring still rated above 0.90. Project matching keeps WRatio (where its leniency is desirable for typos like1-80vsI-80).
Fixed
- Three separate JSON.stringify quote-collision bugs: any inline
onclick="...({...} | tojson)"oronclick="...${JSON.stringify(x)}..."wherexcontained any character that JSON quotes (essentially every real-world string) broke the HTML attribute and silently un-bound the click handler. Surfaced in three places this release; all fixed by switching todata-*attributes plus a trampoline function reading fromthis.dataset:- Location Remove button on the project page
- Metadata-backfill typeahead dropdown (existing project + location pickers)
- Project-merge typeahead dropdown (in the per-project header)
- Project-merge modal too short to show typeahead options without scrolling: modal body's
flex-1 overflow-y-autocollapsed tight; addedmin-height: 480pxto the modal container +min-h-[320px]to the body so the dropdown always has room. - Project location map covered modals: Leaflet's internal panes carry z-indexes 200–800 by default and the map container didn't establish a stacking context, so those z-indexes leaked into the root and outranked modals'
z-50. Fixed by addingisolation: isolateto the map container. delete_assignmentcrashed withAttributeError: the safety check queriedMonitoringSession.start_timebut the actual column isstarted_at. Every DELETE call to/assignments/{id}failed with 500 before doing anything.
Migration Notes
Run on each database before deploying. Both migrations are idempotent and non-destructive.
docker exec terra-view-terra-view-1 python3 /app/backend/migrate_add_location_removed.py docker exec terra-view-terra-view-1 python3 /app/backend/migrate_add_location_sort_order.pyOr sweep all migrations at once (safe — already-applied ones no-op):
for f in backend/migrate_*.py; do docker exec terra-view-terra-view-1 python3 "/app/backend/$(basename $f)" doneNew columns added this release:
monitoring_locations.removed_at(DATETIME, nullable) — NULL means activemonitoring_locations.removal_reason(TEXT, nullable)monitoring_locations.sort_order(INTEGER, default 0) — seeded to alphabetical-index per project on first migration
Deploy order matters: migrations must run BEFORE the new code is up, otherwise the running app will throw 500s on the unrecognized columns. Idempotent migrations make this recoverable but it's better avoided — the v0.11.0 deploy on prod hit this exact window after the v0.10.0 release.
[0.10.0] - 2026-05-14
This release brings terra-view onto the SFM (Seismograph Field Module) event pipeline. Triggered events forwarded by series3-watcher now land in SFM, and terra-view reads from that store as the authoritative source for vibration data. The watcher heartbeat is preserved as a transparent fallback signal.
Added
- SFM Integration: New fleet-wide events page at
/sfmlisting every event ingested by SFM, with filters for serial, date range, false-trigger flag, and limit. Unit detail pages and project-location pages show their own attributed subsets of the same event stream. - Event Detail Modal: Shared across
/sfm, unit detail, and project-location pages — clicking any event opens a rich modal showing peaks per channel (PVS color-coded by magnitude), microphone dB(L) + ZC frequency + time of peak, sensor self-check table with pass/fail per channel, device/recording metadata (firmware, battery, calibration date, geo range), and download buttons for the original Blastware binary and the sidecar JSON. Includes an inline pretty-printed JSON viewer with copy-to-clipboard. - Events Attribution Engine (
backend/services/sfm_events.py): Per-event attribution againstUnitAssignmenttime windows. Events outside any assignment window surface in an "Unattributed" bucket with the nearest-assignment diagnostic (which location, signed delta in days). - Metadata Backfill Tool (
/tools→ Backfill from event metadata): Scans operator-typedprojectandsensor_locationstrings in event sidecars, fuzzy-clusters them viarapidfuzz.WRatio, and proposes retroactiveUnitAssignmentrecords to attribute orphan events. Tracks operator decisions per cluster across re-scans. - Project Tidy Tool (
/tools→ Project Tidy): Fuzzy-detect duplicate projects and bulk-merge them with a single click. Source projects soft-deleted with full audit trail. - Vibration Summary on Project Pages: New roll-up card on vibration project detail pages showing per-location event counts, the project's "Overall Peak" PVS (false triggers excluded), last event timestamp, and a Top Locations by Activity list.
- SFM-Primary Seismograph Status:
emit_status_snapshot()now consults SFM's/db/units(cached 15s) before falling back toEmitter.last_seenfor each seismograph. The fresher signal wins; the choice is recorded in a new per-unitlast_seen_sourcefield. A smallSFM(orange) orHB(gray) badge on each unit's active-table row shows which path is currently driving the status. - Dashboard Rework: Top row reordered to Recent Alerts → Recent Call-Ins (double-wide) → Fleet Summary. Today's Schedule moved to a horizontal collapsible card below the Fleet Map, auto-expanding only when pending actions exist. Recent Call-Ins now sources from a new
/api/recent-event-callinsendpoint backed by SFM event forwards instead of the watcher-heartbeat endpoint. - Sortable Events Tables:
/sfmand unit-detail SFM Events tables now have clickable column headers with ↕/↓/↑ indicators. Default sort is Timestamp DESC. Click same column to toggle direction; click different column to switch and reset to DESC. Pure client-side over cached rows — no re-fetches. - Developer → SFM Admin (
/admin/sfm): Health banner with reachability indicator, terra-view↔SFM connection panel, 4 KPI tiles (known units, total events, stalemonitor_logrows, staleach_sessionsrows), per-unit roll-up table, recent-events table with color-coded forwarding latency (so stale watcher forwards stand out), and a raw API tester for any/api/sfm/*path. - Developer → SLMM Admin (
/admin/slmm): Stripped-down companion page — health, connection info, raw API tester. - Tools Workflow Hub (
/tools): New top-level sidebar entry consolidating Pair Devices, Project Tidy, Metadata Backfill, Reports (info card), and Swap Detection (placeholder). - Sidebar Reorganization: Devices → Projects → Events → Tools → Job Planner → Settings. Devices is now a single entry with internal tabs (All Devices / Seismographs / Sound Level Meters / Modems / Pair Devices) replacing five separate sidebar items.
- Synology Deployment Doc (
docs/SYNOLOGY_DEPLOYMENT.md): End-to-end playbook for migrating the stack to an always-on office NAS — phased rollout (pre-stage, data rsync, watcher repoint, external access, decommission), Tailscale vs reverse-proxy options, rollback plan, and gotchas.
Changed
- Overall Peak excludes false triggers: The project-level "Overall Peak" KPI tile (and the underlying
_compute_stats()function insfm_events.py) now skip events flagged as false triggers when computing the highest PVS, so operators see the highest real event rather than the biggest sensor glitch.false_trigger_countstill includes flagged events so operators can see how many were filtered out. RosterUnit.noteEditing: Inline edit on seismograph cards is more forgiving and now auto-saves on blur.- Sidebar Nav Renamed: Old "Fleet" sidebar entry → "Devices" (renamed because it always meant the device list, not the broader fleet view).
Fixed
- Status drift between watcher heartbeat and actual event arrivals: Seismographs are now reported with whichever signal is more recent — eliminates the case where a unit had recent SFM events but a stale heartbeat (or vice-versa) showed the wrong status.
- Event modal: Record Type always showed "Waveform": Workaround client-side — Record Type now derived from the Blastware filename's last-char code (
H=Histogram,W=Waveform,M=Manual,E=Event,C=Combo). The proper fix lives in SFM's sidecar parser; tracked separately. - Event modal: Mic PSI tile removed: Operators only care about dB(L); the redundant PSI tile was dropped.
Migration Notes
Run on each database before deploying. Every migration is idempotent.
# Cleanest: re-run all migrations in chronological order. # Already-applied migrations no-op safely. for f in backend/migrate_*.py; do docker exec terra-view-terra-view-1 python3 "/app/backend/$(basename $f)" doneMigrations new in this release:
migrate_add_metadata_backfill.py— addsunit_assignments.sourcecolumn andmetadata_backfill_decisionstable for the Metadata Backfill tool
Deployment Notes
SFM_BASE_URL: Confirm prod'sdocker-compose.ymlsets this for the terra-view service (typicallyhttp://sfm:8200for the in-stack SFM container, or an external URL if SFM lives elsewhere).- Watcher repoint: series3-watcher's
sfm_forward_urlshould point athttps://<your-terra-view-host>/api/sfm(proxy-based — no second port forward needed). Watcher composes the full path/db/import/blastware_fileitself.
Downloads
- Soft-remove monitoring locations (