--- module: analytics.adoons title: Adoons Insights tab: Claude order: 40 audience: bcos --- > **Internal · agent reference.** Grounded in `hiveops-analytics` @ 2026-07-01. Only confirmed facts below. ## Ownership - **Service:** `hiveops-analytics` owns this feature end-to-end (controller, service, entity, table). - **Port:** 8089 (prod host 8012). **NGINX:** `/api/analytics/`. External base e.g. `https://api.bcos.cloud/analytics/api/analytics/insights`. - **No AI generation in-repo.** Insights exist only because someone `POST`ed them. Do not assume an in-service generator/Kafka consumer creates them. ## Endpoints (`InsightController`, base `/api/analytics/insights`) | Method | Path | Auth | |--------|------|------| | GET | `/api/analytics/insights` | authenticated (paged) | | GET | `/api/analytics/insights/active` | authenticated | | POST | `/api/analytics/insights` | `hasAnyRole('MSP_ADMIN','BCOS_ADMIN')` | | PATCH | `/api/analytics/insights/{id}/acknowledge` | authenticated | | DELETE | `/api/analytics/insights/active` | authenticated (ack all) | Frontend calls: `getActiveInsights` → `GET /insights/active`; `acknowledgeInsight(id)` → `PATCH /insights/{id}/acknowledge`. ## Auth - Header only: `Authorization: Bearer `. No cookie fallback. - Open paths: `/actuator/health`, `/api/public/**`. Everything else authenticated. - Principal: `AnalyticsPrincipal(userId, email, role, institutionKey)`. ## Data - **DB:** `hiveiq_analytics` (Postgres prod on 10.10.10.188 via ProxyJump; H2 dev). - **Table:** `ai_insights` (cols: `id, category, severity, title, body, snapshot_id, published_at, expires_at, acknowledged, acknowledged_at, acknowledged_by_user_id`). - **FK:** `ai_insights.snapshot_id → analytics_snapshots.id` (nullable). - **Enums:** `category ∈ {ANOMALY,TREND,RECOMMENDATION,SUMMARY}`; `severity ∈ {INFO,WARNING,CRITICAL}`. ## POST body (`PublishInsightRequest`) ```json {"category":"TREND","severity":"WARNING","title":"...","body":"...","snapshotId":123,"expiresAt":"2026-08-01T00:00:00Z"} ``` - `category,severity,title,body` = `@NotBlank`. `snapshotId,expiresAt` optional. - Omit `snapshotId` → links latest snapshot (or null if none). ## Kafka - Insight path uses **no Kafka**. (Analytics produces `analytics.snapshot-ready`; consumes `hiveops.incidents.created/updated/resolved`, `journal.file-parsed` — snapshots only, NOT insights.) ## Active-list rule - `/insights/active` = `acknowledged=false AND (expires_at IS NULL OR expires_at > now)`, ORDER BY `published_at DESC`. - Nightly purge: cron `0 0 3 * * *` deletes acknowledged rows older than 30d. ## Gotchas - Severity enum is `INFO/WARNING/CRITICAL`, but frontend colour map keys `CRITICAL/HIGH/MEDIUM/LOW/INFO` → `WARNING` renders default blue; `HIGH/MEDIUM/LOW` can never come from this backend. - Customer `overview.md` lists CRITICAL/HIGH/MEDIUM/LOW/INFO — does NOT match backend enum. Trust the enum. - Frontend `api.ts` sets NO Authorization header / no axios interceptor, yet backend requires JWT on all `/api/analytics/**`. Token injection is not in frontend code (mechanism unverified — likely NGINX/host shell). - `AdoonsTab.dismiss()` swallows PATCH errors silently → failed acknowledge = card silently stays. - `acknowledge` / `acknowledgeAll` are NOT role-gated — any authenticated user can dismiss fleet-global insights. - POST with bad enum string → 400 (`valueOf` throws). PATCH unknown id → 404. ## Do NOT - Do NOT expect insights to auto-appear — none generate inside analytics; only `POST` creates them. - Do NOT use `Authorization` bearer header assumptions for the frontend; it doesn't set one. - Do NOT invent per-institution scoping — insights are fleet-global, no `institution_key` on the table. - Do NOT route insight writes through Kafka; it's plain REST+JPA. - Do NOT publish `HIGH/MEDIUM/LOW` severities — not valid enum values. - Do NOT put insight reads under `/api/public/**` — they require auth by design. Cross-links: [technical.analytics], [platform.service-ownership].