Full role-tab rollout: every customer module now has Customer/Internal/ Architect/Claude tabs, grounded per-module in real code. Backend serves 296 docs / 96 modules. Grounding surfaced ~20 real bugs across services (filed separately after verification). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4.7 KiB
module, title, tab, order, audience
| module | title | tab | order | audience |
|---|---|---|---|---|
| analytics.adoons | Adoons Insights | Architect | 30 | internal |
Internal · architecture. Written 2026-07-01 from
hiveops-analyticssource. Verify against code before relying on specifics.
Shape of the feature
Adoons Insights is the simplest slice of hiveops-analytics: a single-table CRUD store (ai_insights) exposed over REST, rendered as a card list. Unlike the dashboard/snapshot side of analytics, the insight path is not event-driven and does not use the executor→Kafka→record-store pattern. It's a straight Controller → Service → JpaRepository → Postgres flow.
AdoonsTab.svelte ──GET /insights/active──▶ InsightController ──▶ AiInsightService ──▶ AiInsightRepository ──▶ ai_insights (hiveiq_analytics)
publisher (admin JWT) ──POST /insights──▶ InsightController (@PreAuthorize MSP_ADMIN/BCOS_ADMIN) ──▶ save()
Services involved
- hiveops-analytics — owns everything for this feature: controller, service, entity, table. Port 8089 (prod host 8012). Cross-link [platform.service-ownership].
- Publisher of insights — external to this repo. Analytics has no insight generator, no Kafka consumer that creates insights, and no scheduled producer for them. Insight rows arrive only via authenticated
POST. The intended author is Adoons (hiveops-ai), but that producer is not present in hiveops-analytics (unverified where it lives / whether it's live).
Data flow
There is no Kafka in the insight read/write path. Cross-link [platform.kafka] for the surrounding analytics topics (analytics.snapshot-ready produced by SnapshotEventProducer; hiveops.incidents.* and journal.file-parsed consumed for snapshots) — none of these touch ai_insights.
The only coupling to the rest of analytics is a nullable FK ai_insights.snapshot_id → analytics_snapshots.id. On publish:
- If
snapshotIdis supplied, that snapshot is linked. - Else
AiInsightService.publish()links the latest snapshot (findTopByOrderByCreatedAtDesc()), ornullif none exist.
So an insight can reference the fleet metrics snapshot it was derived from, but the reference is optional and never enforced.
DB tables
| Table | R/W here | Columns of interest |
|---|---|---|
ai_insights |
read + write | id, category, severity, title, body, snapshot_id (FK), published_at, expires_at, acknowledged, acknowledged_at, acknowledged_by_user_id |
analytics_snapshots |
read-only (FK lookup on publish) | id, created_at (used by findTop...) |
DB hiveiq_analytics (PostgreSQL prod / H2 dev). Cross-link [platform.data-architecture]. Flyway manages schema; ai_insights is created in the analytics migration set (V1 init + later columns).
Enums (JPA EnumType.STRING):
Category=ANOMALY | TREND | RECOMMENDATION | SUMMARYSeverity=INFO | WARNING | CRITICAL(entity defaultINFO)
Key API endpoints (InsightController, base /api/analytics/insights)
| Method | Path | Behaviour | Auth |
|---|---|---|---|
| GET | /api/analytics/insights |
Paged list, findAllByOrderByPublishedAtDesc → Page<AiInsightResponse> |
authenticated |
| GET | /api/analytics/insights/active |
findActive(now): unack'd + unexpired, newest first |
authenticated |
| POST | /api/analytics/insights |
publish(req), returns 201 |
hasAnyRole('MSP_ADMIN','BCOS_ADMIN') |
| PATCH | /api/analytics/insights/{id}/acknowledge |
acknowledge(id, principal.userId()); 404 if id unknown; returns 204 |
authenticated |
| DELETE | /api/analytics/insights/active |
acknowledgeAll(now); returns 204 |
authenticated |
Lifecycle / retention
published_atset on@PrePersistif unset.- Active =
acknowledged = false AND (expires_at IS NULL OR expires_at > now). acknowledgeis an atomic JPQLUPDATE;acknowledgeAllclears every unacknowledged row.- Nightly purge:
AiInsightService.purgeOldAcknowledged()—@Scheduled(cron = "0 0 3 * * *"), deletes rows whereacknowledged = true AND acknowledged_at < now-30d.
Security model
SecurityConfig: stateless, CSRF disabled;permitAllonly for/actuator/health+/api/public/**;anyRequest().authenticated().JwtAuthenticationFilterreads the token from theAuthorization: Bearerheader only (no cookie fallback) and builds anAnalyticsPrincipal(userId, email, role, institutionKey).- Only publish is role-restricted; read + acknowledge are open to any authenticated principal (note: no institution scoping on insights — they are fleet-global).
Cross-links: [technical.analytics], [platform.data-architecture], [platform.kafka], [platform.service-ownership].