166 lines
4.5 KiB
Go
166 lines
4.5 KiB
Go
package service
|
||
|
||
import (
|
||
"math"
|
||
"strings"
|
||
)
|
||
|
||
// 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)
|
||
ModelVersion string
|
||
}
|
||
|
||
// 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
|
||
func RiskLevel(score float64) string {
|
||
switch {
|
||
case score <= 30:
|
||
return "green"
|
||
case score <= 60:
|
||
return "yellow"
|
||
case score <= 80:
|
||
return "orange"
|
||
default:
|
||
return "red"
|
||
}
|
||
}
|
||
|
||
// StageCoefficient 蚕房阶段 → 阶段风险系数(Room.Stage 粗粒度映射;
|
||
// 待 #7 蚕匾/批次管理的龄期字段落地后细化)
|
||
func StageCoefficient(stage string) float64 {
|
||
switch stage {
|
||
case "pupa":
|
||
return 0.5 // 核型多角体病 5龄后期至蛹期高发
|
||
case "larva":
|
||
return 0.4 // 软化病 5 龄集中暴发等
|
||
case "moth":
|
||
return 0.2
|
||
case "egg":
|
||
return 0.1
|
||
default:
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// EnvCoefficient 由最新温湿度计算环境风险系数(规则取自规格书 3.2.3)
|
||
func EnvCoefficient(temp, humidity *float64) float64 {
|
||
coef := 0.0
|
||
if humidity != nil {
|
||
switch {
|
||
case *humidity >= 80:
|
||
coef = math.Max(coef, 0.8) // 白僵病等真菌病高湿条件
|
||
case *humidity >= 75:
|
||
coef = math.Max(coef, 0.5)
|
||
}
|
||
}
|
||
if temp != nil {
|
||
if *temp > 30 || *temp < 20 {
|
||
coef = math.Max(coef, 0.5) // 温度突变诱发核型多角体病
|
||
}
|
||
}
|
||
return coef
|
||
}
|