Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 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 | 1x 1x 5x 5x 5x 5x 5x 12x 12x 12x 10x 2x 12x 8x 12x 4x 4x 4x 12x 3x 3x 12x 12x 12x 12x 12x 12x 12x 12x 12x 12x 5x | /**
* AnomalyGuidedCalibrator — marks contamination windows and refits.
*
* When sustained anomalies are detected, marks the window as contaminated
* so that the forecast baseline is recalculated on clean data only.
*/
import type { ICalibrator, DetectionResult, ForecastResult, DataPoint, CalibrationResult, CalibrationMode } from '../types.js'
import { clamp } from './forecast-guided.js'
export class AnomalyGuidedCalibrator implements ICalibrator {
readonly mode: CalibrationMode = 'anomaly-guided'
private contaminated = false
private consecutiveAnomalies = 0
private readonly threshold = 3 // consecutive anomalies to trigger contamination
calibrate(detection: DetectionResult, forecast: ForecastResult, currentPoint: DataPoint): CalibrationResult {
const spread = (forecast.q90[0] ?? 0) - (forecast.q10[0] ?? 0)
const predicted = forecast.predicted[0] ?? currentPoint.value
const residual = spread > 1e-10
? Math.abs(currentPoint.value - predicted) / (spread / 2)
: Math.abs(currentPoint.value - predicted)
if (detection.isAnomaly) {
this.consecutiveAnomalies++
} else {
this.consecutiveAnomalies = 0
this.contaminated = false
}
if (this.consecutiveAnomalies >= this.threshold) {
this.contaminated = true
}
// When contaminated, reduce confidence because baseline may be polluted
const contaminationPenalty = this.contaminated ? 0.3 : 0
const jointConfidence = clamp(detection.confidence - contaminationPenalty, 0, 1)
return {
mode: 'anomaly-guided',
residual,
jointConfidence,
intervalBreached: residual > 1,
isContaminated: this.contaminated,
}
}
}
|