3f0ec8f30b97ab85016a8c671e88036982c6e871
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d5a0163852 |
feat(locations): soft-remove monitoring locations without destroying history
When a client drops a location from scope mid-project (e.g. the office
half of a museum+office monitoring job), operators couldn't previously
mark it as no-longer-active without either deleting it (which would
orphan historical events) or leaving it in the active list looking
deployable. Now there's a proper middle ground.
Data model
- MonitoringLocation gets two new nullable columns:
- removed_at — NULL means active; set means soft-removed
- removal_reason — optional operator note
Migration: backend/migrate_add_location_removed.py (idempotent)
Endpoints
- POST /api/projects/{p}/locations/{l}/remove
Body: { effective_date?: ISO-datetime, reason?: str }
Side effects (cascade):
1. Closes active UnitAssignment rows at this location
(assigned_until = effective_date, status = "completed")
2. Cancels pending ScheduledActions at this location
3. Marks location.removed_at = effective_date
Returns counts of assignments closed + actions cancelled.
- POST /api/projects/{p}/locations/{l}/restore
Clears removed_at + removal_reason. Does NOT auto-reopen
assignments — operator creates new ones if resuming monitoring.
Active-surface filters
- locations-json defaults to active-only; pass include_removed=true
for historical / reporting views. Schedule modal dropdowns now
exclude removed locations automatically.
- Metadata-backfill fuzzy matcher excludes removed locations from
proposed targets (don't want backfill creating new assignments at
decommissioned locations).
- Vibration-summary per_location rollup includes removed locations
(so historical event totals stay accurate) but tags each with
removed_at so the UI can show a badge.
UI
- Project detail page's Monitoring Locations section now splits into:
Active locations (full card with Assign / Edit / Remove / Delete)
Removed locations (collapsed <details>, greyed cards, Restore button,
shows removal date + reason)
- New per-card "Remove" button → opens confirmation modal explaining
the cascade, with optional effective-date (defaults to now,
backdateable) and reason fields.
- Unit detail's SFM Events attribution cell shows a small "removed"
badge next to historical attributions whose location is no longer
active. Same pattern in vibration_summary's top-locations list.
- Soft-removal indicator surfaced through the events_for_unit
attribution payload as location_removed_at.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e15481884a |
feat(nav,stats): Events sidebar entry + 'Overall Peak' excludes false triggers
Two related operator-facing improvements after the nav reorg. 1) Events as a top-level sidebar entry. The /sfm page (fleet-wide event database) was demoted to Settings → Developer in the previous reorg. Bringing it back to main nav as "Events" — operators do reach for the cross-project, sortable event list, so it earns a top-level slot. Sidebar now (7 items): Dashboard · Devices · Projects · Events · Tools · Job Planner · Settings Settings → Developer card pointing at /sfm is removed. /sfm page title/subtitle updated from "SFM Event Data" to just "Events". URL unchanged. 2) "Peak PVS" KPI tile becomes "Overall Peak" and excludes false triggers from the calculation. When operators ask "what's the biggest event at this location/unit/ project?" they mean the biggest REAL event, not the biggest sensor glitch. A single mis-flagged false trigger could otherwise dominate the tile (the 14.13 in/s spike at Loc 1 was a prime example). backend/services/sfm_events.py: - _compute_stats() skips false_trigger=True events when computing peak_pvs / peak_pvs_at / peak_pvs_serial. Continues counting them in false_trigger_count so the separate "False Triggers" tile still reflects what got filtered out. last_event unchanged (recency, not magnitude). - Same change automatically propagates to events_for_unit() and vibration_summary_for_project() — both call _compute_stats(). Templates: "Peak PVS" → "Overall Peak" in 3 KPI tile locations (vibration_location_detail.html, partials/projects/vibration_summary .html, unit_detail.html). The physical-quantity name "Peak Vector Sum" in the event-detail modal stays — that's the actual physics term, not a summary stat. Verified end-to-end: Overall Peak renders on real data; peak event false_trigger flag confirmed False. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
63bd6ad8a2 |
feat(sfm): project-level vibration events roll-up
Phase 3 of the SFM integration. Adds a "Project-wide vibration events"
KPI card to the Vibration tab of every project detail page, summarising
event activity across all of that project's vibration MonitoringLocations.
Backend:
- backend/services/sfm_events.py: vibration_summary_for_project() helper.
Concurrently fans out events_for_location() across every vibration
location in the project; aggregates total events, peak PVS (with the
location it occurred at), last-event timestamp, false-trigger count;
and produces a per-location breakdown sorted by event count.
- backend/routers/project_locations.py: new GET /api/projects/{p}/
vibration_summary endpoint returning an HTML partial (HTMX-friendly,
matches the locations-list HTMX pattern already used on this page).
Frontend:
- templates/partials/projects/vibration_summary.html: new partial with
four KPI tiles (total, peak PVS + linked location + date, last event,
false triggers) and a "Top locations by activity" mini-list showing
the top 5 by event count. Empty-state copy when the project has no
vibration locations yet.
- templates/projects/detail.html: HTMX-load the new summary above the
locations list inside the Vibration tab.
Verified against terra-view-alpha: 24 events across "Loc 1 - 78 poop
street", peak PVS 14.1351 in/s.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
bc5a151faa |
feat(sfm): per-unit event history with attribution + Unattributed bucket
Phase 2 of the SFM integration. Adds a "SFM Events" section to the
seismograph unit detail page (/unit/{id}). Every event SFM has for the
serial is shown, with each event annotated by which project/location
assignment window it falls into. Events outside every assignment window
get the "⚠ Unattributed" badge plus a "<N>d before/after <nearest location>"
hint — that's the operator's signal that backdating an assignment (Phase 1
edit-pencil) will absorb the orphan events.
Backend:
- backend/services/sfm_events.py: new events_for_unit() helper. Fetches
all events for the serial via SFM /db/events (one call, ceiling 5000),
loads every UnitAssignment for the unit + resolves MonitoringLocation +
Project names, then annotates each event with attribution or
nearest_assignment (signed delta_days). Bucket filter: all /
attributed / unattributed. Stats always reflect the full event set so
the "Unattributed" KPI tile is meaningful regardless of which bucket
is being viewed.
- backend/routers/units.py: new GET /api/units/{unit_id}/events with
bucket / date-range / false_trigger / limit query params. 404s on
unknown unit_id; returns an empty payload for non-seismograph
device_types so the page can render the section conditionally.
Frontend (templates/unit_detail.html):
- New "SFM Events" section between "Deployment History" and "Timeline",
styled to match the existing card pattern (border-t divider, same
heading weight).
- Hidden by default; revealed only when currentUnit.device_type ===
'seismograph' after the unit data loads.
- Four KPI tiles: Total Events / Unattributed (highlighted amber when
> 0) / Peak PVS / Last Event.
- Filters: Bucket (all|attributed|unattributed), From/To, False
Triggers, Limit, + Refresh.
- Event table with Attribution column. Attributed rows link to the
project/location detail page; unattributed rows are tinted amber
and show "<N>d before/after <nearest location>" with a link to the
nearest location.
- Empty-state copy varies by bucket: e.g. unattributed-with-zero shows
"✅ All events for this unit are attributed to a project/location".
Verified end-to-end against BE11529 (81 events total, 24 attributed,
57 unattributed — all 57 unattributed events emitted within hours of
the assignment start, which means backdating the assignment by a day
would attribute every one of them).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
df771a87de |
feat(sfm): wire SFM events into project-location detail page
Phase 1 of the SFM project/location integration. When viewing a vibration
monitoring location, operators now see the events that were actually
recorded there — fanned out across every seismograph that was ever
assigned to that location (handles mid-project unit swaps).
Backend:
- backend/services/sfm_events.py: new events_for_location() async helper.
Walks UnitAssignment rows for the location (active + closed), intersects
each assignment's [assigned_at, assigned_until] window with the requested
filter, and concurrently queries SFM /db/events for each (serial, window)
pair via httpx.AsyncClient. Unions, sorts newest-first, computes summary
stats (event count, peak PVS + when/who, last event, false-trigger count)
over the full set, and trims to the user's display limit. Over-fetches
per-window (up to 5000) so stats stay accurate even with a small display
limit.
- backend/routers/project_locations.py: new GET endpoint
/api/projects/{project_id}/locations/{location_id}/events. Validates
project/location pairing (404 on mismatch). SLM locations return an
empty payload rather than 404 so the frontend can render gracefully.
Frontend:
- templates/vibration_location_detail.html: new "Events" tab on the
location detail page. KPI tiles (total / peak PVS / last event / false
triggers), "Seismographs deployed at this location" assignment list
(transparency: shows each assignment's date range and contributed event
count), date / false-trigger / limit filters, and the paginated event
table. Lazy-loaded on first tab visit; manual refresh button.
Architectural notes:
- SFM remains the single source of truth for events. No event sync; live
HTTP per page load.
- UnitAssignment is the join key (not MonitoringSession).
- Events whose timestamp falls outside every assignment window are NOT
surfaced here. Those orphan events get a dedicated "Unattributed
events" view on the per-unit detail page in Phase 2.
Out of scope (this commit):
- Phase 2 (per-unit history view) and Phase 3 (project-level roll-up)
reuse this helper but ship separately.
- Phase 4 (deprecating deployment_records) is independent.
- Extracting the event-table JS to a shared file is a follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|