package condition import ( "encoding/json" "fmt" "strconv" "strings" "aiaa-notification-service/internal/model" ) // Evaluate checks all conditions against data. Returns true if ALL conditions pass. // An empty conditions slice always returns true. func Evaluate(conditions []model.Condition, data map[string]interface{}) bool { if len(conditions) == 0 { return true } for _, c := range conditions { if !evaluateOne(c, data) { return false } } return true } func evaluateOne(c model.Condition, data map[string]interface{}) bool { fieldVal, fieldExists := data[c.Field] switch c.Op { case "exists": return fieldExists case "not_exists": return !fieldExists case "eq": if !fieldExists { return false } return fmt.Sprintf("%v", fieldVal) == c.Value case "ne": if !fieldExists { return false } return fmt.Sprintf("%v", fieldVal) != c.Value case "contains": if !fieldExists { return false } return strings.Contains(fmt.Sprintf("%v", fieldVal), c.Value) case "gt", "gte", "lt", "lte": if !fieldExists { return false } return compareNumeric(fieldVal, c.Value, c.Op) default: return false } } func compareNumeric(fieldVal interface{}, value string, op string) bool { fv, err := toFloat64(fieldVal) if err != nil { return false } cv, err := strconv.ParseFloat(value, 64) if err != nil { return false } switch op { case "gt": return fv > cv case "gte": return fv >= cv case "lt": return fv < cv case "lte": return fv <= cv } return false } func toFloat64(v interface{}) (float64, error) { switch val := v.(type) { case float64: return val, nil case float32: return float64(val), nil case int: return float64(val), nil case int64: return float64(val), nil case string: return strconv.ParseFloat(val, 64) case json.Number: return val.Float64() default: return 0, fmt.Errorf("cannot convert %T to float64", v) } }