Skip to content

Local Anomaly Detection with Alert Badges#283

Draft
etiennechabert wants to merge 15 commits into
mainfrom
auto-claude/026-local-anomaly-detection-with-alert-badges
Draft

Local Anomaly Detection with Alert Badges#283
etiennechabert wants to merge 15 commits into
mainfrom
auto-claude/026-local-anomaly-detection-with-alert-badges

Conversation

@etiennechabert

Copy link
Copy Markdown
Owner

Implement client-side statistical anomaly detection that runs on the local dataset. Detect sudden cost increases (>2 standard deviations from rolling average) at the (entity × service) level. Surface anomalies as alert badges on the Cost Overview and entity names. Show anomaly detail with the suspected cause (new resource, usage spike, pricing change).

etiennechabert and others added 15 commits May 9, 2026 19:43
Add AnomalyId branded type and anomaly detection types to support
local statistical anomaly detection at (entity × service) level.

Types include:
- AnomalyId: unique identifier for detected anomalies
- AnomalyDetectionParams: parameters for running detection
- Anomaly: detected anomaly with severity and deviation metrics
- AnomalyResult: collection of detected anomalies with counts
- AnomalyDetailParams: parameters for fetching anomaly details
- AnomalyDetailResult: detailed breakdown with daily costs and resources

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… DuckDB

Implemented statistical anomaly detection using DuckDB:
- buildAnomalyDetectionQuery: Calculates rolling averages and standard deviations
- Detects cost spikes >N standard deviations from baseline
- Supports configurable lookback window and threshold
- parseAnomalyDetectionResult: Converts raw query results to typed Anomaly objects
- buildAnomalyDetailQuery: Retrieves time series for individual anomalies
- parseAnomalyDetailResult: Parses detail results with daily costs
- Uses parameterized queries for SQL injection protection
- Follows existing query builder patterns from builder.ts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comprehensive test coverage for the anomaly detection algorithm:
- buildAnomalyDetectionQuery: SQL generation with rolling avg/stddev
- parseAnomalyDetectionResult: result parsing and severity classification
- buildAnomalyDetailQuery: time series detail query generation
- parseAnomalyDetailResult: detail result parsing
- Edge cases: year boundaries, large lookback periods, various thresholds

All 39 tests passing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…face

- Added queryAnomalies() and queryAnomalyDetail() query methods
- Added dismissAnomaly() and restoreAnomaly() for managing dismissed state
- Imported anomaly types from query.ts and AnomalyId from branded.ts
- Exported all anomaly types from types/index.ts
- All methods follow existing CostApi patterns with JSDoc comments

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Created packages/desktop/src/main/handlers/anomaly.ts with IPC handlers for:
  - query:anomalies - detect cost anomalies using rolling average + stddev
  - query:anomaly-detail - get time series detail for a specific anomaly
  - anomaly:dismiss - mark an anomaly as dismissed
  - anomaly:restore - restore a dismissed anomaly

- Modified packages/desktop/src/main/handlers/context.ts:
  - Added dismissedAnomalies state to AppState
  - Added getDismissedAnomalies(), dismissAnomaly(), restoreAnomaly() methods
  - Dismissed anomalies persisted to dismissed-anomalies.json

- Modified packages/desktop/src/main/handlers/query.ts:
  - Registered anomaly handlers via registerAnomalyHandlers()

- Modified packages/desktop/src/preload/preload.ts:
  - Exposed anomaly IPC methods to renderer process

- Modified packages/core/src/index.ts:
  - Exported anomaly detection functions from core package

- Modified packages/ui/src/__fixtures__/mock-api.ts:
  - Added stub implementations for anomaly methods to satisfy CostApi interface

All changes follow existing patterns from query-costs.ts. Type check passes.
…st Overview

Created use-anomalies hook following the use-query pattern with QueryState,
cancellation handling, and retry logic. Added anomaly detection state to
CustomView with 30-day lookback and 2σ threshold. State will be consumed
by widgets in subtask-4-2 for badge integration.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Convert number to string in template literals (anomaly-badge.tsx)
- Remove unnecessary type assertion (mock-api.ts)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…art entities

- Add anomaliesByEntity prop to TopNBarChartProps for passing anomaly data
- Import AnomalyBadge component and AnomalySeverity type
- Render AnomalyBadge before entity names when anomalies exist
- Fix unnecessary type assertion in anomaly.ts handler

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added realistic anomaly mock data to MockCostApi:
- Imported anomaly types and asAnomalyId helper
- Created mockAnomalies array with high and medium severity examples
- Created anomalyResult with 2 anomalies (1 high, 1 medium)
- Created mockAnomalyDetail with daily costs, rolling avg, stddev, and resources
- Updated queryAnomalies() to return anomalyResult
- Updated queryAnomalyDetail() to return mockAnomalyDetail
- Anomalies align with existing trend data (ml and platform entities)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
… verification prep

Complete the anomaly detection integration by wiring anomaly state through the widget layer:

- Add QueryState and AnomalyResult to WidgetCommonProps interface
- Pass anomaliesState from CustomView to all widget renderers
- Implement buildAnomaliesMap() in TopNBarWidget to group anomalies by entity
- Add upgradeSeverity() helper to select highest severity per entity
- Wire anomaliesByEntity prop to TopNBarChart component
- Fix linting errors in mock-api.ts (remove unnecessary type assertions, fix non-null assertion)

TypeScript compilation and linting verified. Ready for manual E2E testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Complete the anomaly detail modal integration:

- Add modal imports (AnomalyDetailModal, useCostApi) to top-n-bar-chart.tsx
- Extend anomaliesByEntity type to include service and detectedDate fields
- Update buildAnomaliesMap() to include service/detectedDate from anomalies
- Add selectedAnomaly state to track which modal to show
- Implement handleAnomalyBadgeClick() to capture entity and anomaly data
- Implement handleAnomalyDismiss() to dismiss anomaly via API
- Add onClick handler to AnomalyBadge with cursor-pointer styling
- Render AnomalyDetailModal with all required props when anomaly selected
- Use Promise.then() pattern to avoid no-misused-promises linting error

TypeScript compilation and ESLint verified. Ready for E2E testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Detector now resolves groupBy via resolveField instead of raw SQL
  interpolation, restoring SecurityError validation for unknown
  dimensions and applying the same alias/normalize machinery the rest
  of the app uses.
- Apply user filters and cost-scope exclusions to detection by routing
  through setupQuery (matches buildCostQuery / buildTrendQuery shape).
- Detail query now takes the dimension and stddev threshold as params
  and filters via the resolved field expression — previously hardcoded
  account_id, so clicking a badge for a tag-based dimension hit nothing.
- IPC handler converts RawRow to typed shapes via toNum/toStr/toBool
  helpers instead of `as unknown as` casts; placeholder Anomaly with
  zeros is gone (modal showed wrong currentCost/severity for every
  real result before).
- Modal receives the full Anomaly via props rather than refetching
  metadata server-side; it only queries the daily-trend time series.
- Empty "Top 5 Affected Resources" UI section dropped (was always
  empty; resource-level breakdown wasn't implemented).
- After dismiss, CustomView bumps a nonce so the anomaly query
  refetches and the badge actually disappears.
- Detection is gated behind an off-by-default checkbox in the dashboard
  header — the feature isn't in SPEC.md and shouldn't run heavy window
  queries unprompted on every render.
- Replaced 39 SQL-substring snapshot tests with focused unit tests for
  the parsing/severity logic plus three real DuckDB integration tests
  that catch regressions like the hardcoded-account_id bug above.
- Removed dynamic-import cast violations and inline `import('@costgoblin/core').AnomalyId`
  types in context.ts; use top-level imports + asAnomalyId.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant