Local Anomaly Detection with Alert Badges#283
Draft
etiennechabert wants to merge 15 commits into
Draft
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).