One code-grounded internal guide per backend service (Responsibility / Data / Kafka / API surface / Gotchas), authored by parallel per-service agents reading each service's own source, then an adversarial verify pass (DB names, topics, endpoints spot-checked against code — 0 hard errors). Several guides correctly document CODE over stale CLAUDE.md (remote = HTTP long-poll not WebSocket; analytics = has a DB, not stateless; devices = has its own backend). Unconfirmed facts are marked (unverified). audience: internal, all DRAFT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.5 KiB
module, title, tab, order, audience
| module | title | tab | order | audience |
|---|---|---|---|---|
| technical.analytics | Analytics — fleet dashboard aggregation & snapshots | Overview | 10 | internal |
DRAFT · internal. Written 2026-07-01 from hiveops-analytics source. Verify against code before relying on specifics.
Responsibility
hiveops-analytics computes fleet dashboard analytics by aggregating data from hiveops-incident and hiveops-journal (and hiveops-mgmt). It exposes read-oriented dashboard endpoints (fleet health, uptime, incident trends, cash intelligence, journal coverage, agent insights) and persists periodic snapshots plus AI insights ("Adoons"-style callouts) in its own database. Runs on port 8089 (PORT env override). Cross-link [platform.service-ownership].
Note: the in-repo CLAUDE.md still describes the service as "stateless — no database," but the code has since gained its own DB (analytics_snapshots, ai_insights), a snapshot scheduler, and Kafka consumers. Treat it as stateful with an aggregation cache.
Data
Database: hiveiq_analytics (PostgreSQL in prod, H2 in-memory in dev). Cross-link [platform.data-stores].
Prod connection uses split env vars DB_HOST / DB_PORT / DB_NAME / DB_USERNAME / DB_PASSWORD (JDBC URL assembled in application.yml), not a single DB_URL. DB_NAME defaults to hiveiq_analytics.
| Table | Purpose |
|---|---|
analytics_snapshots |
Point-in-time fleet metrics: atm counts (total/connected/disconnected/never-connected), incidents by severity (open/critical/high/medium/low), tasks (pending/in-progress/completed-today), transactions today + failed, plus opened_incidents_today / closed_incidents_today (V2). Unique on (snapshot_date, snapshot_hour). |
ai_insights |
Generated insight cards: category, severity, title, body, optional snapshot_id FK, expires_at, acknowledged / acknowledged_at / acknowledged_by_user_id. |
Flyway migrations in src/main/resources/db/migration/ (V1 init, V2 open/close incident columns, V3 snapshot_hour type fix). Cache TTL for aggregations via ANALYTICS_CACHE_TTL (default 300s).
Kafka
Topic constants in kafka/AnalyticsKafkaConfig.java. Cross-link [platform.kafka].
Produced
| Topic | Producer class | Notes |
|---|---|---|
analytics.snapshot-ready |
SnapshotEventProducer |
Emitted (key "snapshot", SnapshotReadyEvent{snapshotDate, snapshotHour}) after a snapshot is collected by SnapshotCollectionService. |
Consumed
| Topic | Listener class |
|---|---|
hiveops.incidents.created |
IncidentEventConsumer |
hiveops.incidents.updated |
IncidentEventConsumer |
hiveops.incidents.resolved |
IncidentEventConsumer |
journal.file-parsed |
JournalFileParsedConsumer |
Consumer group hiveops-analytics, auto-offset-reset: latest. Incident/journal events trigger real-time snapshot collect(); a @Scheduled(cron = "0 0 */6 * * *") job in SnapshotCollectionService is the 6-hour fallback.
API surface
Most controllers sit under base /api/analytics; snapshots and insights have their own sub-paths; there is one unauthenticated public path.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/analytics/dashboard/overview |
Fleet overview: atm counts, incidents, tasks, transactions |
| GET | /api/analytics/fleet-health |
Fleet health summary |
| GET | /api/analytics/uptime |
Uptime metrics |
| GET | /api/analytics/incident-trends |
Incident trends over a period |
| GET | /api/analytics/cash-intelligence |
Cash intelligence metrics |
| GET | /api/analytics/journal-coverage |
Journal coverage/reporting metrics |
| GET | /api/analytics/agents |
Agent insights |
| GET | /api/analytics/snapshots/latest |
Most recent snapshot |
| GET | /api/analytics/snapshots |
List snapshots |
| GET | /api/analytics/snapshots/range |
Snapshots for a date range |
| GET | /api/analytics/insights |
All AI insights |
| GET | /api/analytics/insights/active |
Active (unacknowledged, unexpired) insights |
| POST | /api/analytics/insights |
Create an insight — hasAnyRole('MSP_ADMIN','BCOS_ADMIN') |
| GET | /api/public/stats |
Public stats (no auth) |
Not exhaustive.
Gotchas
/api/public/**and/actuator/healtharepermitAll()inSecurityConfig; everything else requires an authenticated JWT./api/public/statsis intentionally unauthenticated — do not put sensitive fleet data there.- Only
POST /api/analytics/insightsis role-gated (hasAnyRole('MSP_ADMIN','BCOS_ADMIN')); the many GET endpoints are.anyRequest().authenticated()with no role restriction. - Service-to-service auth forwards the caller's JWT to incident/journal/mgmt (
WebClientConfig); there is no separate service account. Downstream calls fail if the user token lacks access. - Downstream URLs are env-configured (
INCIDENT_SERVICE_URL,JOURNAL_SERVICE_URL,MGMT_SERVICE_URL), defaulting to in-cluster hostnames (hiveops-incident-backend:8080,hiveops-journal:8087,hiveops-mgmt:8080). - Snapshots are event-driven with a 6h cron fallback; if incident/journal Kafka events stop flowing, dashboard freshness degrades to the 6-hour cadence.
- Prod uses split DB_HOST/DB_PORT/DB_NAME env vars (unlike hiveops-journal, which expects a single
DB_URL). devprofile uses H2 withddl-auto: create-dropand Flyway disabled;produses PostgreSQL withddl-auto: validateand Flyway enabled — schema drift surfaces only in prod validation.