Files
hiveops-guide/backend/src/main/resources/content/analytics/fleet-health/architect.md
T
johannes f21c783d8c fix(guide): gate Architect + Claude tabs to BCOS_ADMIN only (#16)
The 162 Architect/Claude docs were tagged 'audience: dev'. The 'dev' tier
resolves to isInternal() = ROLE_MSP_ADMIN || ROLE_BCOS_ADMIN, so every
MSP_ADMIN could read full internal architecture: source paths, DB tables,
service ports, NGINX routes. Verified live on bcos.dev as msp_a@msp1.bcos.dev.

Retag them to 'audience: bcos' (isBcos, BCOS_ADMIN only). Content-only change;
the 'dev' tier mapping is deliberately left alone so the 15 Internal, 35
Overview and 66 Testing docs stay MSP-visible.

Also add platform.guide-access documenting the audience tiers and the
per-role visibility matrix, so the access model is queryable from the Guide
instead of re-derived from source each time.

Verified: MSP module count unchanged (116 across 17 apps); no module loses
all its tabs. Guide is bcos.dev only, not deployed to production.

Closes #16

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 17:55:07 -04:00

5.4 KiB

module, title, tab, order, audience
module title tab order audience
analytics.fleet-health Fleet Health Architect 30 bcos

How it's built. Fleet Health is a synchronous fan-out aggregation — not a snapshot/Kafka read. Grounded in FleetHealthController + FleetHealthService (hiveops-analytics) and the frontend FleetHealthTab.svelte. Cross-link [platform.data-architecture], [platform.kafka], [platform.service-ownership].

Services involved

Service Role in this feature
hiveops-analytics (owner, port 8089) Serves GET /api/analytics/fleet-health; orchestrates + aggregates. Holds no state for this tab.
hiveops-incident Source of ATM connectivity summary, recent activity events, and open incidents.
hiveops-journal (port 8087) Source of open journal gaps.

Cross-link [platform.service-ownership]: analytics owns the aggregation shape; incident/journal own the underlying records. Analytics reads no incident/journal DB tables directly — only their HTTP APIs.

Data flow (request-time, no Kafka)

Browser (FleetHealthTab.svelte)
  └─ GET /api/analytics/fleet-health   (Bearer JWT)
       └─ FleetHealthService.buildFleetHealth(callerToken)   [forwards the SAME JWT]
            ├─ incidentClient GET /api/atms/summary                       → {total,connected,disconnected,neverConnected}
            ├─ incidentClient GET /api/journal-events/activity?hoursBack=24 → [{atmId,eventType,...}]
            ├─ incidentClient GET /api/incidents/status/open              → [ ...open incidents ]
            └─ journalClient  GET /api/journal/gaps?status=OPEN           → [{atmId,...}]
       └─ aggregate → FleetHealthResponse (JSON)
  • No executor→Kafka→record-store pattern here. That pattern belongs to the analytics snapshot pipeline (SnapshotCollectionServiceanalytics.snapshot-readyanalytics_snapshots), which powers other tabs (Overview/Trends), not Fleet Health. This tab bypasses the DB entirely. Cross-link [platform.kafka].
  • Each downstream call is independently try/catch-wrapped; a failure logs a warning and substitutes an empty result, so the response degrades gracefully to partial/zero data.

Aggregation logic (in FleetHealthService)

  • Connectivity: pulls total/connected/disconnected/neverConnected from /api/atms/summary; connectivityPct = connected*100.0/total (0 when total=0).
  • Event Breakdown: activity events (last 24h) grouped by category via classifyEventType(eventType). Category map is seeded with HARDWARE, CASH, NETWORK, POWER, AUTH, SYSTEM, OTHER (all 0), then incremented.
    • Notable: there is no OPERATIONS category in the backend — the frontend categoryColors defines one, but classifyEventType never emits it; unmatched types fall to OTHER.
  • Top Problematic ATMs: groups the same 24h events by atmId, counts, sorts desc, limit(5)ProblematicAtm{atmId,eventCount}.
  • Open Incidents: openIncidents.size() of /api/incidents/status/open.
  • ATMs with Journal Gaps: distinct atmId count from /api/journal/gaps?status=OPEN.

Event-type → category mapping (source of truth: classifyEventType)

Category Event types
POWER POWER_OFF
HARDWARE CARD_READER_FAIL, DISPENSER_JAM, SELF_TEST_FAILED, APPLICATION_ERROR_CARD, APPLICATION_ERROR_DISPENSER, APPLICATION_ERROR_ENCRYPTION, APPLICATION_ERROR_PRINTER, APPLICATION_ERROR_PINPAD, MIXED_MEDIA_ERROR, CASH_ACCEPTOR_ERROR, ENCRYPTOR_PINPAD_ERROR
CASH CASSETTE_LOW, CASSETTE_EMPTY, CASSETTE_ABNORMAL
NETWORK NETWORK_DISCONNECTED, APPLICATION_ERROR_NETWORK
AUTH AUTHENTICATION_FAILURE
SYSTEM ATM_OUT_OF_SERVICE, APPLICATION_ERROR, APPLICATION_ERROR_JOURNAL, AGENT_LOG_ERROR, EJOURNAL_DB_WARNING, EJOURNAL_DB_CRITICAL
OTHER anything unmatched / null

DB tables read/written

  • This feature: none. No reads or writes to hiveiq_analytics. (Snapshot tables analytics_snapshots / ai_insights exist for other tabs — see [technical.analytics].)
  • Underlying records live in incident and journal databases and are reached only via HTTP.

Key API endpoints

Method Path Service Used for
GET /api/analytics/fleet-health analytics The tab's single data call
GET /api/atms/summary incident Connectivity donut
GET /api/journal-events/activity?hoursBack=24 incident Event breakdown + top problematic
GET /api/incidents/status/open incident Open incidents count
GET /api/journal/gaps?status=OPEN journal ATMs with journal gaps

Frontend

  • FleetHealthTab.svelte renders four cards (Connectivity donut, Event Breakdown bars, Top Problematic ATMs, Alerts & Gaps) from FleetHealthData.
  • API base resolves from window.__APP_CONFIG__.apiUrl (fallback http://localhost:8089/api/analytics); call is analyticsAPI.getFleetHealth()GET {base}/fleet-health.
  • FleetHealthData.topProblematicAtms carries a deviceId "Phase 2 alias" alongside atmId — part of the ongoing ATM→device rename. Cross-link [platform.service-ownership].

Auth

  • JWT-authenticated; no role restriction (SecurityConfig: .anyRequest().authenticated()). The caller's token is forwarded verbatim to incident/journal (WebClientConfig clients, no service account). Cross-link [platform.auth].