fix(ai): filter out noise from anomalies and status changes
Critical changes to surface only actionable insights: 1. Anomalies now require at least 50% deviation from baseline - '1.0x baseline' values filtered out (statistically significant but not actionable) - Must be >1.5x above OR <0.5x below baseline to report 2. Status changes filter out startup noise - 'unknown → running' is just system starting, not a real state change - Backups removed from main list (they have dedicated section) 3. Only show genuinely interesting changes: - Config changes, migrations, restarts, deletions - Things that require operator attention This massively reduces noise while keeping high-signal alerts.
This commit is contained in:
parent
5138b01ed8
commit
0f22906767
2 changed files with 31 additions and 16 deletions
|
|
@ -291,14 +291,22 @@ export const AIOverviewTable: Component<{ showWhenEmpty?: boolean }> = (props) =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Changes - only show meaningful changes, not just "created" (infrastructure detection)
|
// Changes - only show meaningful changes, not noise
|
||||||
// Valuable changes: config, status, migrated, restarted, deleted, backed_up
|
// Skip: created (just patrol discovering resources)
|
||||||
// Skip: created (just means AI patrol discovered the resource)
|
// Skip: backed_up (backups have their own section, no need to duplicate here)
|
||||||
const meaningfulChanges = changes().filter(c => c.change_type !== 'created');
|
// Skip: status changes from "unknown" (just startup, not real state changes)
|
||||||
|
const meaningfulChanges = changes().filter(c => {
|
||||||
|
if (c.change_type === 'created') return false;
|
||||||
|
if (c.change_type === 'backed_up') return false;
|
||||||
|
// Filter out startup noise: "unknown → running" or "unknown → stopped"
|
||||||
|
if (c.change_type === 'status' && c.description?.includes('unknown →')) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
const visibleChanges = showAllChanges()
|
const visibleChanges = showAllChanges()
|
||||||
? meaningfulChanges
|
? meaningfulChanges
|
||||||
: meaningfulChanges.slice(0, CHANGE_LIMIT);
|
: meaningfulChanges.slice(0, CHANGE_LIMIT);
|
||||||
|
|
||||||
|
|
||||||
for (const change of visibleChanges) {
|
for (const change of visibleChanges) {
|
||||||
const changeTypeBadgeClass: Record<string, string> = {
|
const changeTypeBadgeClass: Record<string, string> = {
|
||||||
deleted: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
deleted: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
||||||
|
|
|
||||||
|
|
@ -291,33 +291,40 @@ func (s *Store) CheckResourceAnomalies(resourceID string, metrics map[string]flo
|
||||||
|
|
||||||
for metric, value := range metrics {
|
for metric, value := range metrics {
|
||||||
severity, zScore, baseline := s.CheckAnomaly(resourceID, metric, value)
|
severity, zScore, baseline := s.CheckAnomaly(resourceID, metric, value)
|
||||||
if severity != AnomalyNone {
|
if severity != AnomalyNone && baseline != nil {
|
||||||
|
// Compute ratio: current value / baseline mean
|
||||||
|
ratio := value / baseline.Mean
|
||||||
|
|
||||||
|
// Filter out statistically significant but practically meaningless anomalies
|
||||||
|
// Users don't care about "1.0x baseline" or small deviations
|
||||||
|
// Require at least 50% deviation from baseline to report
|
||||||
|
if ratio >= 0.5 && ratio <= 1.5 {
|
||||||
|
continue // Too close to baseline to be actionable
|
||||||
|
}
|
||||||
|
|
||||||
report := AnomalyReport{
|
report := AnomalyReport{
|
||||||
ResourceID: resourceID,
|
ResourceID: resourceID,
|
||||||
Metric: metric,
|
Metric: metric,
|
||||||
CurrentValue: value,
|
CurrentValue: value,
|
||||||
ZScore: zScore,
|
ZScore: zScore,
|
||||||
Severity: severity,
|
Severity: severity,
|
||||||
|
BaselineMean: baseline.Mean,
|
||||||
|
BaselineStdDev: baseline.StdDev,
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseline != nil {
|
// Generate human-readable description
|
||||||
report.BaselineMean = baseline.Mean
|
direction := "above"
|
||||||
report.BaselineStdDev = baseline.StdDev
|
if zScore < 0 {
|
||||||
|
direction = "below"
|
||||||
// Generate human-readable description
|
|
||||||
ratio := value / baseline.Mean
|
|
||||||
direction := "above"
|
|
||||||
if zScore < 0 {
|
|
||||||
direction = "below"
|
|
||||||
}
|
|
||||||
report.Description = formatAnomalyDescription(metric, ratio, direction, severity)
|
|
||||||
}
|
}
|
||||||
|
report.Description = formatAnomalyDescription(metric, ratio, direction, severity)
|
||||||
|
|
||||||
anomalies = append(anomalies, report)
|
anomalies = append(anomalies, report)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return anomalies
|
return anomalies
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatAnomalyDescription generates a human-readable anomaly description
|
// formatAnomalyDescription generates a human-readable anomaly description
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue