commit for version used in evaluation of thesis
This commit is contained in:
commit
72635dc7b9
27 changed files with 6084 additions and 0 deletions
114
internal/detect/mad_test.go
Normal file
114
internal/detect/mad_test.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package detect
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/pata1704/guenther/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMADDetector_Score(t *testing.T) {
|
||||
detector := NewMADDetector(3.0, []float64{10.0}, []float64{1.0})
|
||||
|
||||
// 1. Score a normal value
|
||||
res, err := detector.Score(types.FeatureVector{
|
||||
Timestamp: time.Now(),
|
||||
NormalizedVector: []float64{11},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, res.IsAnomaly, "Value 11 should not be an anomaly")
|
||||
|
||||
// 2. Score an extreme outlier
|
||||
res, err = detector.Score(types.FeatureVector{
|
||||
Timestamp: time.Now(),
|
||||
NormalizedVector: []float64{100},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, res.IsAnomaly, "Value 100 should be an anomaly")
|
||||
assert.Greater(t, res.Score, 3.0)
|
||||
}
|
||||
|
||||
func TestMADDetector_CalibrationStability(t *testing.T) {
|
||||
// 1. Create a detector that auto-calibrates on 100 idle vectors.
|
||||
detector := NewMADDetectorAutoCalibrate(3.5, 100)
|
||||
now := time.Now()
|
||||
|
||||
// 2. Feed 99 perfectly idle vectors.
|
||||
// They should all use "Identity" fallback and return low scores (or 0 if val is 0).
|
||||
for i := 0; i < 99; i++ {
|
||||
fv := types.FeatureVector{
|
||||
Timestamp: now.Add(time.Duration(i) * time.Second),
|
||||
NormalizedVector: []float64{0.0, 0.0},
|
||||
}
|
||||
res, err := detector.Score(fv)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0.0, res.Score)
|
||||
assert.Contains(t, res.Method, "warmup")
|
||||
}
|
||||
|
||||
// 3. Feed the 100th vector. This triggers calibration.
|
||||
// Since all 100 vectors were 0, the learned medians will be 0 and mads will be 0.
|
||||
fv100 := types.FeatureVector{
|
||||
Timestamp: now.Add(100 * time.Second),
|
||||
NormalizedVector: []float64{0.0, 0.0},
|
||||
}
|
||||
res100, err := detector.Score(fv100)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0.0, res100.Score)
|
||||
// After this call, mads should be [0.0, 0.0] but clamped to 0.01 during Score.
|
||||
|
||||
// 4. Feed the 101st vector: A "normal" burst (e.g. 1.0 baseline IQR).
|
||||
// Without the floor, this would be 1.0 / (1.48 * 0) -> infinity (clamped).
|
||||
// With the floor (0.01), it should be 1.0 / (1.4826 * 0.01) ≈ 67.45.
|
||||
fv101 := types.FeatureVector{
|
||||
Timestamp: now.Add(101 * time.Second),
|
||||
NormalizedVector: []float64{1.0, 0.0},
|
||||
}
|
||||
res101, err := detector.Score(fv101)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check that the score is contained.
|
||||
// 1.0 / (1.4826 * 0.01) = 67.449
|
||||
assert.InDelta(t, 67.449, res101.Score, 0.1)
|
||||
assert.True(t, res101.IsAnomaly)
|
||||
assert.Equal(t, "MAD", res101.Method) // No longer "warmup"
|
||||
|
||||
// 5. Test with a very small variance but not 0.
|
||||
// Suppose learned MAD was 0.0001. Score for val=1.0 would be 1.0 / 0.000148... ≈ 6745.
|
||||
// Our floor (0.01) should still clamp this to 67.45.
|
||||
detector.mu.Lock()
|
||||
detector.mads = []float64{0.0001, 0.0}
|
||||
detector.medians = []float64{0.0, 0.0}
|
||||
detector.mu.Unlock()
|
||||
|
||||
resSmall, err := detector.Score(fv101)
|
||||
assert.NoError(t, err)
|
||||
assert.InDelta(t, 67.449, resSmall.Score, 0.1)
|
||||
}
|
||||
|
||||
func TestMADDetector_IdentityPrior(t *testing.T) {
|
||||
detector := NewMADDetectorAutoCalibrate(3.5, 10)
|
||||
|
||||
// Feature vector with a deviation of 2.0 baseline IQR.
|
||||
// Using identity prior (mad=1.0), the score should be:
|
||||
// score = |2.0| / (1.4826 * 1.0) = 2.0 / 1.4826 ≈ 1.3489
|
||||
// Wait, scoreIdentity uses 0.6745 directly: math.Abs(val) / 0.6745
|
||||
// 2.0 / 0.6745 ≈ 2.965
|
||||
fv := types.FeatureVector{
|
||||
NormalizedVector: []float64{2.0},
|
||||
}
|
||||
res, _ := detector.Score(fv)
|
||||
assert.InDelta(t, 2.965, res.Score, 0.1)
|
||||
assert.False(t, res.IsAnomaly) // 2.96 < 3.5
|
||||
|
||||
// Feature vector with deviation of 3.0.
|
||||
// score = 3.0 / 0.6745 ≈ 4.44
|
||||
fv2 := types.FeatureVector{
|
||||
NormalizedVector: []float64{3.0},
|
||||
}
|
||||
res2, _ := detector.Score(fv2)
|
||||
assert.InDelta(t, 4.44, res2.Score, 0.1)
|
||||
assert.True(t, res2.IsAnomaly)
|
||||
assert.Contains(t, res2.Details, "identity prior")
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue