Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14,242 changes: 8,120 additions & 6,122 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

799 changes: 799 additions & 0 deletions src/components/dashboard/AnomalyVisualization.jsx

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ const NAV_ITEMS: NavItem[] = [
{ id: 'featureFlags', label: 'Flags', icon: '🚩' },
{ id: 'codeReview', label: 'Code Review', icon: '🔍' },
{ id: 'txPatterns', label: 'AI Patterns', icon: '🧠' },
{ id: 'aiDescription', label: 'AI Descriptions', icon: '✨' },
{ id: 'contractRecommendations', label: 'Contract AI', icon: '💡' },
{ id: 'capacityPlanning', label: 'Capacity AI', icon: '📈' },
{ id: 'anomalyViz', label: 'Anomaly Viz', icon: '◉' },
{ id: 'systemHealth', label: 'Health', icon: '⚕' },
{ id: 'monitoringDashboards', label: 'Monitoring', icon: '📊' },
{ id: 'throughputForecast', label: 'Forecast', icon: '📈' },
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/usePreload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const TAB_LOADERS: Record<string, () => Promise<unknown>> = {
liveActivity: () => import('../components/dashboard/LiveActivityFeed'),
claimableBalances: () => import('../components/dashboard/ClaimableBalances'),
dataExport: () => import('../components/dashboard/DataExport'),
personalization: () => import('../components/dashboard/PersonalizationPanel'),
anomalyViz: () => import('../components/dashboard/AnomalyVisualization'),
}

const preloaded = new Set<string>()
Expand Down
86 changes: 86 additions & 0 deletions src/lib/__tests__/anomalyClustering.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest'
import { kmeans, autoKmeans } from '../anomalyClustering'

describe('kmeans', () => {
it('returns empty result for empty data', () => {
const result = kmeans([], 3)
expect(result.clusters).toEqual([])
expect(result.assignments).toEqual([])
expect(result.inertia).toBe(0)
})

it('clusters simple 2D data into correct groups', () => {
const data = [
[0, 0], [0.1, 0.1], [-0.1, -0.1],
[10, 10], [10.1, 10.1], [9.9, 9.9],
[-10, -10], [-10.1, -10.1], [-9.9, -9.9],
]
const result = kmeans(data, 3, { maxIter: 50 })

expect(result.clusters).toHaveLength(3)
expect(result.assignments).toHaveLength(9)
expect(result.centroids).toHaveLength(3)
expect(result.iterations).toBeGreaterThan(0)
expect(result.inertia).toBeGreaterThan(0)

const a0 = result.assignments[0]
const a1 = result.assignments[1]
const a2 = result.assignments[2]
expect(a0).toBe(a1)
expect(a1).toBe(a2)
})

it('k=1 puts everything in one cluster', () => {
const data = [[0, 0], [1, 1], [2, 2], [10, 10]]
const result = kmeans(data, 1)
expect(result.clusters).toHaveLength(1)
expect(result.clusters[0]).toHaveLength(4)
expect(result.assignments.every((a) => a === 0)).toBe(true)
})

it('handles k larger than data size', () => {
const data = [[1, 2], [3, 4]]
const result = kmeans(data, 10)
expect(result.clusters).toHaveLength(2)
expect(result.assignments).toHaveLength(2)
})

it('converges or reaches max iterations', () => {
const data = Array.from({ length: 20 }, () => [Math.random() * 10, Math.random() * 10])
const result = kmeans(data, 3, { maxIter: 10, tolerance: 1e-6 })
expect(result.iterations).toBeLessThanOrEqual(10)
expect(result.clusters.some((c) => c.length > 0)).toBe(true)
})

it('handles 1D data', () => {
const data = [[0], [0.5], [10], [10.5], [20], [20.5]]
const result = kmeans(data, 3)
expect(result.clusters).toHaveLength(3)
expect(result.assignments).toHaveLength(6)
})
})

describe('autoKmeans', () => {
it('returns a reasonable k value', () => {
const data = [
[0, 0], [0.1, 0.1], [-0.1, -0.1],
[10, 10], [10.1, 10.1],
[20, 20], [20.1, 20.1],
]
const result = autoKmeans(data, { maxK: 5 })
expect(result.k).toBeGreaterThanOrEqual(1)
expect(result.k).toBeLessThanOrEqual(5)
expect(result.clusters).toHaveLength(result.k)
expect(result.assignments).toHaveLength(data.length)
})

it('handles 0 or 1 data points', () => {
const empty = autoKmeans([])
expect(empty.k).toBe(1)
expect(empty.clusters).toEqual([])

const single = autoKmeans([[1, 2]])
expect(single.k).toBe(1)
expect(single.clusters).toEqual([[0]])
})
})
93 changes: 93 additions & 0 deletions src/lib/__tests__/dimensionalityReduction.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { describe, it, expect } from 'vitest'
import { pca } from '../dimensionalityReduction'

describe('pca', () => {
it('returns empty projected array for empty input', () => {
const result = pca([], 2)
expect(result.projected).toEqual([])
expect(result.mean).toEqual([])
expect(result.std).toEqual([])
})

it('reduces 2D data to 1 component', () => {
const X = [
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 6],
]
const result = pca(X, 1)
expect(result.projected).toHaveLength(5)
expect(result.projected[0]).toHaveLength(1)
expect(result.components).toHaveLength(1)
expect(result.mean).toHaveLength(2)
expect(result.std).toHaveLength(2)
})

it('reduces high-dim data to 2 components', () => {
const X = Array.from({ length: 20 }, () => [
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
Math.random() * 100,
])
const result = pca(X, 2)
expect(result.projected).toHaveLength(20)
expect(result.projected[0]).toHaveLength(2)
expect(result.components).toHaveLength(2)
})

it('produces explained variance ratios that sum to ~1', () => {
const X = Array.from({ length: 30 }, () => [
Math.random() * 50,
Math.random() * 50,
Math.random() * 50,
])
const result = pca(X, 3)
const totalVar = result.explainedVariance.reduce((s, v) => s + v, 0)
expect(totalVar).toBeCloseTo(1, 1)
})

it('handles single-dimensional data', () => {
const X = [[1], [2], [3], [4], [5]]
const result = pca(X, 1)
expect(result.projected).toHaveLength(5)
expect(result.projected[0]).toHaveLength(1)
})

it('handles data with zero variance', () => {
const X = [
[5, 5],
[5, 5],
[5, 5],
]
const result = pca(X, 2)
expect(result.projected).toHaveLength(3)
expect(result.projected[0]).toHaveLength(2)
expect(result.components).toHaveLength(2)
})

it('returns proper mean and std for standardization', () => {
const X = [
[10, 100],
[20, 200],
[30, 300],
]
const result = pca(X, 2)
expect(result.mean[0]).toBeCloseTo(20, 5)
expect(result.mean[1]).toBeCloseTo(200, 5)
expect(result.std[0]).toBeGreaterThan(0)
expect(result.std[1]).toBeGreaterThan(0)
})

it('requesting k larger than dimensions returns k components', () => {
const X = [
[1, 2],
[3, 4],
]
const result = pca(X, 5)
expect(result.components).toHaveLength(2)
expect(result.projected[0]).toHaveLength(2)
})
})
Loading
Loading