From d297412d8a8d598bf147dc361a23124db5e0a61c Mon Sep 17 00:00:00 2001 From: serversdown Date: Fri, 15 May 2026 05:25:19 +0000 Subject: [PATCH] feat(locations): show event count on vibration cards instead of sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For vibration projects, "Sessions: 0" on every location card was misleading — monitoring sessions don't exist under the watcher-forward pipeline. The relevant number is how many SFM events have been attributed to the location. get_project_locations now fans out events_for_location() concurrently across all vibration locations in the project (via asyncio.gather) and injects event_count into each item's payload. Sound locations are unchanged — they still show session_count. The template already had the conditional rendering ready from the previous commit: {% if item.event_count is defined and item.location.location_type == 'vibration' %} {{ event_count }} events {% else %} Sessions: {{ session_count }} {% endif %} so this commit is purely the data-layer change that activates it. Co-Authored-By: Claude Opus 4.7 --- backend/routers/project_locations.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/backend/routers/project_locations.py b/backend/routers/project_locations.py index 8c89793..83bd19b 100644 --- a/backend/routers/project_locations.py +++ b/backend/routers/project_locations.py @@ -154,6 +154,24 @@ async def get_project_locations( # Order by operator-set sort_order, then name as a stable tie-breaker. locations = query.order_by(MonitoringLocation.sort_order, MonitoringLocation.name).all() + # For vibration locations, fan out event counts via SFM concurrently + # so the card layout can show "{N} events" instead of "Sessions: 0" + # (sessions don't really exist for the watcher-forward pipeline). + # Sound locations skip this and keep showing session counts. + event_counts: dict[str, int] = {} + vibration_locations = [l for l in locations if l.location_type == "vibration"] + if vibration_locations: + import asyncio + from backend.services.sfm_events import events_for_location + results = await asyncio.gather( + *(events_for_location(db, l.id, limit=1) for l in vibration_locations), + return_exceptions=True, + ) + for loc, res in zip(vibration_locations, results): + if isinstance(res, Exception): + continue # leave event_counts[loc.id] unset → template falls back + event_counts[loc.id] = (res.get("stats") or {}).get("event_count", 0) or 0 + # Enrich with assignment info, splitting active vs removed. active_data: list = [] removed_data: list = [] @@ -184,6 +202,8 @@ async def get_project_locations( "assigned_unit": assigned_unit, "session_count": session_count, } + if location.id in event_counts: + item["event_count"] = event_counts[location.id] if location.removed_at is None: active_data.append(item) else: