feat: 修复 AI 风险语义并隔离 Mock 数据

This commit is contained in:
weijuesen
2026-08-14 01:16:50 +08:00
parent 839ba91354
commit 74d1948d68
29 changed files with 650 additions and 113 deletions
+108 -12
View File
@@ -1,21 +1,117 @@
package service
import "math"
import (
"math"
"strings"
)
// RiskInput 风险评分输入(各系数取值 0~1)
// RiskRuleVersion 当前风险规则版本;权重仍沿用试点公式,待数据校准后升版。
const RiskRuleVersion = "risk-v2-2026.08.14"
// RiskInput 风险评分输入。nil 表示未采集,不能按 0 参与归一化。
type RiskInput struct {
AI float64 // AI 识别置信度(权重 0.5
Env float64 // 环境风险系数(权重 0.2
Stage float64 // 饲养阶段风险系数(权重 0.15)
Uniformity float64 // 群体整齐度偏离度(权重 0.15)
AI *float64 // AI 异常概率(权重 0.5
Env *float64 // 环境风险系数(权重 0.2
Stage *float64 // 饲养阶段风险系数(权重 0.15)
Uniformity *float64 // 群体整齐度偏离度(权重 0.15)
ModelVersion string
}
// ComputeRiskScore 按规格书 3.1.4 公式计算 0~100 风险分:
// 风险分 = 0.5×AI置信度 + 0.2×环境系数 + 0.15×阶段系数 + 0.15×整齐度偏离度
func ComputeRiskScore(in RiskInput) float64 {
score := 0.5*in.AI + 0.2*in.Env + 0.15*in.Stage + 0.15*in.Uniformity
score = math.Max(0, math.Min(1, score))
return score * 100
// RiskAssessment 可解释风险输出。
type RiskAssessment struct {
Score float64 `json:"score"`
Level string `json:"level"`
Confidence string `json:"confidence"`
ModelVersion string `json:"modelVersion"`
RuleVersion string `json:"ruleVersion"`
Components map[string]*float64 `json:"components"`
Missing []string `json:"missing"`
}
// ComputeRiskScore 按可用权重归一化计算 0~100 风险分,缺失组件返回 null。
// 当前 0.5/0.2/0.15/0.15 是待试点校准规则,不是科学结论。
func ComputeRiskScore(in RiskInput) RiskAssessment {
defs := []struct {
key string
weight float64
value *float64
}{
{"aiAbnormalProbability", 0.5, in.AI},
{"environment", 0.2, in.Env},
{"stage", 0.15, in.Stage},
{"uniformity", 0.15, in.Uniformity},
}
components := make(map[string]*float64, len(defs))
missing := make([]string, 0, len(defs))
weighted := 0.0
totalWeight := 0.0
for _, def := range defs {
if def.value == nil {
components[def.key] = nil
missing = append(missing, def.key)
continue
}
value := clamp01(*def.value)
components[def.key] = &value
weighted += def.weight * value
totalWeight += def.weight
}
score := 0.0
if totalWeight > 0 {
score = math.Round(clamp01(weighted/totalWeight)*10000) / 100
}
return RiskAssessment{
Score: score,
Level: RiskLevel(score),
Confidence: RiskConfidence(in),
ModelVersion: in.ModelVersion,
RuleVersion: RiskRuleVersion,
Components: components,
Missing: missing,
}
}
// RiskConfidence 当前仅给出 low/medium/unknown,避免把未校准规则描述为 high。
func RiskConfidence(in RiskInput) string {
if in.AI == nil {
return "unknown"
}
if *in.AI >= 0.25 {
return "medium"
}
return "low"
}
// AbnormalProbability 从 AI 检测结果计算异常概率:healthy/unknown 不贡献风险。
// abnormalClasses 可选;缺省时任一非 healthy/unknown 类别都视为异常类别。
func AbnormalProbability(detections []AIDetection, abnormalClasses ...string) float64 {
configured := make(map[string]struct{}, len(abnormalClasses))
for _, class := range abnormalClasses {
if class = strings.ToLower(strings.TrimSpace(class)); class != "" {
configured[class] = struct{}{}
}
}
best := 0.0
for _, d := range detections {
class := strings.ToLower(strings.TrimSpace(d.ClassName))
if class == "" || class == "healthy" || class == "unknown" {
continue
}
if len(configured) > 0 {
if _, ok := configured[class]; !ok {
continue
}
}
if d.Confidence > best {
best = d.Confidence
}
}
return clamp01(best)
}
func clamp01(v float64) float64 {
return math.Max(0, math.Min(1, v))
}
// RiskLevel 按规格书 3.1.4 分级:绿 0-30 / 黄 31-60 / 橙 61-80 / 红 81-100