feat: implement scenario-driven sales SOP platform
This commit is contained in:
129
codes/internal/run/engine.go
Normal file
129
codes/internal/run/engine.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func matchCondition(raw json.RawMessage, answers map[string]interface{}) (bool, error) {
|
||||
if len(raw) == 0 || string(raw) == "{}" || string(raw) == "null" {
|
||||
return true, nil
|
||||
}
|
||||
var rule map[string]interface{}
|
||||
if err := json.Unmarshal(raw, &rule); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if all, ok := rule["all"].([]interface{}); ok {
|
||||
for _, item := range all {
|
||||
object, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
return false, fmt.Errorf("invalid all condition")
|
||||
}
|
||||
matched, err := matchRule(object, answers)
|
||||
if err != nil || !matched {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
if any, ok := rule["any"].([]interface{}); ok {
|
||||
for _, item := range any {
|
||||
object, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
matched, err := matchRule(object, answers)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if matched {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return matchRule(rule, answers)
|
||||
}
|
||||
|
||||
func matchRule(rule map[string]interface{}, answers map[string]interface{}) (bool, error) {
|
||||
field, _ := rule["field"].(string)
|
||||
operator, _ := rule["operator"].(string)
|
||||
if field == "" || operator == "" {
|
||||
return false, fmt.Errorf("condition field and operator are required")
|
||||
}
|
||||
actual, exists := answers[field]
|
||||
expected := rule["value"]
|
||||
switch operator {
|
||||
case "exists":
|
||||
return exists && actual != nil && fmt.Sprint(actual) != "", nil
|
||||
case "not_exists":
|
||||
return !exists || actual == nil || fmt.Sprint(actual) == "", nil
|
||||
case "equals":
|
||||
return reflect.DeepEqual(normalizeValue(actual), normalizeValue(expected)), nil
|
||||
case "not_equals":
|
||||
return !reflect.DeepEqual(normalizeValue(actual), normalizeValue(expected)), nil
|
||||
case "contains":
|
||||
return strings.Contains(strings.ToLower(fmt.Sprint(actual)), strings.ToLower(fmt.Sprint(expected))), nil
|
||||
case "greater_than", "less_than":
|
||||
left, leftOK := toFloat(actual)
|
||||
right, rightOK := toFloat(expected)
|
||||
if !leftOK || !rightOK {
|
||||
return false, nil
|
||||
}
|
||||
if operator == "greater_than" {
|
||||
return left > right, nil
|
||||
}
|
||||
return left < right, nil
|
||||
case "in":
|
||||
values, ok := expected.([]interface{})
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
for _, value := range values {
|
||||
if reflect.DeepEqual(normalizeValue(actual), normalizeValue(value)) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unsupported operator: %s", operator)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeValue(value interface{}) interface{} {
|
||||
switch typed := value.(type) {
|
||||
case json.Number:
|
||||
if number, err := typed.Float64(); err == nil {
|
||||
return number
|
||||
}
|
||||
case int:
|
||||
return float64(typed)
|
||||
case int64:
|
||||
return float64(typed)
|
||||
case uint64:
|
||||
return float64(typed)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func toFloat(value interface{}) (float64, bool) {
|
||||
switch typed := value.(type) {
|
||||
case float64:
|
||||
return typed, true
|
||||
case float32:
|
||||
return float64(typed), true
|
||||
case int:
|
||||
return float64(typed), true
|
||||
case int64:
|
||||
return float64(typed), true
|
||||
case uint64:
|
||||
return float64(typed), true
|
||||
case json.Number:
|
||||
result, err := typed.Float64()
|
||||
return result, err == nil
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user