File size: 3,387 Bytes
729628a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import Box from '@mui/material/Box';
import Switch from '@mui/material/Switch';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import type { AnomalyConfig } from '../../types/labeleval';

type Detectors = AnomalyConfig['detectors'];

function DetectorRow({
  label,
  enabled,
  onToggle,
  children,
}: {
  label: string;
  enabled: boolean;
  onToggle: (enabled: boolean) => void;
  children?: React.ReactNode;
}) {
  return (
    <Box sx={{ mb: 1 }}>
      <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
        <Typography variant="body2" sx={{ fontWeight: 600 }}>
          {label}
        </Typography>
        <Switch size="small" checked={enabled} onChange={(e) => onToggle(e.target.checked)} />
      </Box>
      {enabled ? <Box sx={{ display: 'flex', gap: 1, mt: 0.5 }}>{children}</Box> : null}
    </Box>
  );
}

function NumField({
  label,
  value,
  onChange,
  step,
}: {
  label: string;
  value: number;
  onChange: (v: number) => void;
  step?: number;
}) {
  return (
    <TextField
      size="small"
      type="number"
      label={label}
      value={value}
      inputProps={{ step: step ?? 1 }}
      onChange={(e) => onChange(Number(e.target.value))}
      sx={{ flex: 1, '& input': { fontSize: 13 } }}
    />
  );
}

/** Column 2 of the rare-event configuration grid: classical anomaly detectors. */
export default function AnomalyDetectionConfig({
  detectors,
  onChange,
}: {
  detectors: Detectors;
  onChange: (detectors: Detectors) => void;
}) {
  const set = <K extends keyof Detectors>(key: K, patch: Partial<Detectors[K]>) =>
    onChange({ ...detectors, [key]: { ...detectors[key], ...patch } });

  return (
    <Box>
      <DetectorRow label="KNN" enabled={detectors.knn.enabled} onToggle={(v) => set('knn', { enabled: v })}>
        <NumField label="k" value={detectors.knn.k} onChange={(v) => set('knn', { k: v })} />
      </DetectorRow>
      <DetectorRow label="LOF" enabled={detectors.lof.enabled} onToggle={(v) => set('lof', { enabled: v })}>
        <NumField
          label="n_neighbors"
          value={detectors.lof.n_neighbors}
          onChange={(v) => set('lof', { n_neighbors: v })}
        />
      </DetectorRow>
      <DetectorRow
        label="Isolation Forest"
        enabled={detectors.isolation_forest.enabled}
        onToggle={(v) => set('isolation_forest', { enabled: v })}
      >
        <NumField
          label="n_estimators"
          value={detectors.isolation_forest.n_estimators}
          onChange={(v) => set('isolation_forest', { n_estimators: v })}
        />
      </DetectorRow>
      <DetectorRow label="One-Class SVM" enabled={detectors.ocsvm.enabled} onToggle={(v) => set('ocsvm', { enabled: v })}>
        <NumField label="nu" value={detectors.ocsvm.nu} step={0.01} onChange={(v) => set('ocsvm', { nu: v })} />
      </DetectorRow>
      <DetectorRow
        label="DBSCAN"
        enabled={detectors.dbscan.enabled}
        onToggle={(v) => set('dbscan', { enabled: v })}
      >
        <NumField label="eps" value={detectors.dbscan.eps} step={0.05} onChange={(v) => set('dbscan', { eps: v })} />
        <NumField
          label="min_samples"
          value={detectors.dbscan.min_samples}
          onChange={(v) => set('dbscan', { min_samples: v })}
        />
      </DetectorRow>
    </Box>
  );
}