// Package realtimedata define real time data operation functions package realtimedata import ( "context" "errors" "fmt" "modelRT/constants" "modelRT/logger" ) // RealTimeAnalyzer 接口定义了实时数据分析和事件触发的通用方法 type RealTimeAnalyzer interface { AnalyzeAndTriggerEvent(ctx context.Context, conf *ComputeConfig, realTimeValues []float64) } // teEventThresholds define struct of store the telemetry float point threshold parsed from conf field cause type teEventThresholds struct { up float64 upup float64 down float64 downdown float64 isFloatCause bool } // parseTEThresholds define func to parse telemetry thresholds by casue map func parseTEThresholds(cause map[string]any) (teEventThresholds, error) { t := teEventThresholds{} floatKeys := map[string]*float64{ "upup": &t.upup, "up": &t.up, "down": &t.down, "downdown": &t.downdown, } for key, ptr := range floatKeys { if value, exists := cause[key]; exists { if floatVal, ok := value.(float64); ok { *ptr = floatVal t.isFloatCause = true } else { return teEventThresholds{}, fmt.Errorf("key:%s type is incorrect. expected float64, actual %T", key, value) } } } // quickly check mutual exclusion if _, exists := cause["edge"]; exists && t.isFloatCause { return teEventThresholds{}, errors.New("cause config error: 'up/down' keys and 'edge' key are mutually exclusive, but both found") } return t, nil } // getTEBreachType define func to determine which type of out-of-limit the telemetry real time data belongs to func getTEBreachType(value float64, t teEventThresholds) string { if t.upup > 0 && value > t.upup { return constants.TelemetryUpUpLimit } if t.up > 0 && value > t.up { return constants.TelemetryUpLimit } if t.downdown > 0 && value < t.downdown { return constants.TelemetryDownDownLimit } if t.down > 0 && value < t.down { return constants.TelemetryDownLimit } return "" } // TEAnalyzer define struct of store the thresholds required for telemetry and implements the analysis logic. type TEAnalyzer struct { Thresholds teEventThresholds } // AnalyzeAndTriggerEvent 实现了 RealTimeAnalyzer 接口 func (t *TEAnalyzer) AnalyzeAndTriggerEvent(ctx context.Context, conf *ComputeConfig, realTimeValues []float64) { analyzeTEDataLogic(ctx, conf, t.Thresholds, realTimeValues) } // 封装原 analyzeTEDataAndTriggerEvent 的核心逻辑 func analyzeTEDataLogic(ctx context.Context, conf *ComputeConfig, thresholds teEventThresholds, realTimeValues []float64) { windowSize := conf.minBreachCount if windowSize <= 0 { logger.Error(ctx, "variable minBreachCount is invalid or zero, analysis skipped", "minBreachCount", windowSize) return } // mark whether any events have been triggered in this batch var eventTriggered bool breachTriggers := map[string]bool{ "up": false, "upup": false, "down": false, "downdown": false, } // implement slide window to determine breach counts for i := 0; i <= len(realTimeValues)-windowSize; i++ { window := realTimeValues[i : i+windowSize] firstValueBreachType := getTEBreachType(window[0], thresholds) if firstValueBreachType == "" { continue } allMatch := true for j := 1; j < windowSize; j++ { currentValueBreachType := getTEBreachType(window[j], thresholds) if currentValueBreachType != firstValueBreachType { allMatch = false break } } if allMatch { // in the case of a continuous sequence of out-of-limit events, check whether this type of event has already been triggered in the current batch of data if !breachTriggers[firstValueBreachType] { // trigger event logger.Warn(ctx, "event triggered by sliding window", "breach_type", firstValueBreachType, "value", window[windowSize-1]) breachTriggers[firstValueBreachType] = true eventTriggered = true } } } // TODO 调用 EventRT 接口进行时间推送 if eventTriggered { fmt.Println("--- 本次数据切片分析结束:已标记触发事件 ---") } else { fmt.Println("--- 本次数据切片分析结束:未检测到持续越限,不触发事件 ---") } } // tiEventThresholds define struct of store the telesignal float point threshold parsed from conf field cause type tiEventThresholds struct { edge string isFloatCause bool } // parseTEThresholds define func to parse telesignal thresholds by casue map func parseTIThresholds(cause map[string]any) (tiEventThresholds, error) { edgeKey := "edge" t := tiEventThresholds{ isFloatCause: false, } if value, exists := cause[edgeKey]; exists { if strVal, ok := value.(string); ok { switch strVal { case "raising", "falling": t.edge = strVal return t, nil default: return tiEventThresholds{}, fmt.Errorf("key:%s value is incorrect, actual value %s. expected 'raising' or 'falling'", edgeKey, strVal) } } else { return tiEventThresholds{}, fmt.Errorf("key:%s already exists but type is incorrect. expected string, actual %T", edgeKey, value) } } return tiEventThresholds{}, fmt.Errorf("cause map is invalid for telesignal: missing required key '%s'", edgeKey) } // getTIBreachType define func to determine which type of out-of-limit the telesignal real time data belongs to func getTIBreachType(currentValue float64, previousValue float64, t tiEventThresholds) string { if t.edge == constants.TelesignalRaising { if previousValue == 0.0 && currentValue == 1.0 { return constants.TIBreachTriggerType } } else if t.edge == constants.TelesignalFalling { if previousValue == 1.0 && currentValue == 0.0 { return constants.TIBreachTriggerType } } return "" } // TIAnalyzer define struct of store the thresholds required for remote signaling and implements the analysis logic type TIAnalyzer struct { Thresholds tiEventThresholds } // AnalyzeAndTriggerEvent 实现了 RealTimeAnalyzer 接口 func (t *TIAnalyzer) AnalyzeAndTriggerEvent(ctx context.Context, conf *ComputeConfig, realTimeValues []float64) { analyzeTIDataLogic(ctx, conf, t.Thresholds, realTimeValues) } // 封装原 analyzeTIDataAndTriggerEvent 的核心逻辑 (使用预计算优化版本) func analyzeTIDataLogic(ctx context.Context, conf *ComputeConfig, thresholds tiEventThresholds, realTimeValues []float64) { windowSize := conf.minBreachCount if windowSize <= 0 { logger.Error(ctx, "variable minBreachCount is invalid or zero, analysis skipped", "minBreachCount", windowSize) return } numDataPoints := len(realTimeValues) if numDataPoints < 2 { logger.Info(ctx, "data points less than 2, no change event possible, analysis skipped", "data_points", numDataPoints) return } // pre calculate the change event type for all adjacent point pairs numChanges := numDataPoints - 1 changeBreachTypes := make([]string, numChanges) for i := range numChanges { previousValue := realTimeValues[i] currentValue := realTimeValues[i+1] changeBreachTypes[i] = getTIBreachType(currentValue, previousValue, thresholds) } if numChanges < windowSize { logger.Error(ctx, "number of change events is less than window size, analysis skipped", "num_changes", numChanges, "window_size", windowSize) return } var eventTriggered bool breachTriggers := map[string]bool{ constants.TIBreachTriggerType: false, } for i := 0; i <= numChanges-windowSize; i++ { windowBreachTypes := changeBreachTypes[i : i+windowSize] firstBreachType := windowBreachTypes[0] if firstBreachType == "" { continue } allMatch := true for j := 1; j < windowSize; j++ { if windowBreachTypes[j] != firstBreachType { allMatch = false break } } if allMatch { if !breachTriggers[firstBreachType] { finalValueIndex := i + windowSize logger.Warn(ctx, "event triggered by sliding window", "breach_type", firstBreachType, "value", realTimeValues[finalValueIndex]) breachTriggers[firstBreachType] = true eventTriggered = true } } } // TODO 调用 EventRT 接口进行时间推送 if eventTriggered { fmt.Println("--- 本次数据切片分析结束:已标记触发事件 ---") } else { fmt.Println("--- 本次数据切片分析结束:未检测到持续越限,不触发事件 ---") } }