feat: 修复 AI 风险语义并隔离 Mock 数据
This commit is contained in:
@@ -1,43 +1,120 @@
|
||||
package service
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func f(v float64) *float64 { return &v }
|
||||
func ptr(v float64) *float64 { return &v }
|
||||
|
||||
func TestComputeRiskScoreWeights(t *testing.T) {
|
||||
// 全 1:0.5*1 + 0.2*1 + 0.15*1 + 0.15*1 = 1 → 100
|
||||
if s := ComputeRiskScore(RiskInput{AI: 1, Env: 1, Stage: 1, Uniformity: 1}); s != 100 {
|
||||
t.Errorf("全 1 应得 100,实际 %.2f", s)
|
||||
got := ComputeRiskScore(RiskInput{AI: ptr(1), Env: ptr(1), Stage: ptr(1), Uniformity: ptr(1)})
|
||||
if got.Score != 100 {
|
||||
t.Errorf("全 1 应得 100,实际 %.2f", got.Score)
|
||||
}
|
||||
// 仅 AI 置信度 1:0.5*1 = 0.5 → 50
|
||||
if s := ComputeRiskScore(RiskInput{AI: 1}); s != 50 {
|
||||
t.Errorf("仅 AI=1 应得 50,实际 %.2f", s)
|
||||
if len(got.Missing) != 0 {
|
||||
t.Errorf("全组件可用时不应有 missing,实际 %v", got.Missing)
|
||||
}
|
||||
// 0.5*0.8 + 0.2*0.5 = 0.5
|
||||
if s := ComputeRiskScore(RiskInput{AI: 0.8, Env: 0.5}); s != 50 {
|
||||
t.Errorf("0.8/0.5 应得 50,实际 %.2f", s)
|
||||
if got.Components["aiAbnormalProbability"] == nil || got.Components["uniformity"] == nil {
|
||||
t.Errorf("全组件可用时 components 不应为 null: %v", got.Components)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeRiskScoreNormalizesMissingComponents(t *testing.T) {
|
||||
got := ComputeRiskScore(RiskInput{AI: ptr(1)})
|
||||
if got.Score != 100 {
|
||||
t.Errorf("仅 AI=1 归一化后应得 100,实际 %.2f", got.Score)
|
||||
}
|
||||
if len(got.Missing) != 3 {
|
||||
t.Errorf("missing 应为 environment/stage/uniformity,实际 %v", got.Missing)
|
||||
}
|
||||
if got.Components["environment"] != nil {
|
||||
t.Error("缺失 environment 应序列化为 null")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthyHighConfidenceDoesNotIncreaseRisk(t *testing.T) {
|
||||
got := AbnormalProbability([]AIDetection{{ClassName: "healthy", Confidence: .95}})
|
||||
if got != 0 {
|
||||
t.Fatalf("healthy 高置信度不应产生异常概率,实际 %v", got)
|
||||
}
|
||||
assessment := ComputeRiskScore(RiskInput{AI: ptr(got)})
|
||||
if assessment.Score != 0 {
|
||||
t.Fatalf("healthy 高置信度风险分应为 0,实际 %.2f", assessment.Score)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSickHighConfidenceIncreasesRisk(t *testing.T) {
|
||||
got := AbnormalProbability([]AIDetection{{ClassName: "sick", Confidence: .9}})
|
||||
if got != .9 {
|
||||
t.Fatalf("sick 高置信度异常概率应为 0.9,实际 %v", got)
|
||||
}
|
||||
assessment := ComputeRiskScore(RiskInput{AI: ptr(got)})
|
||||
if assessment.Score != 90 {
|
||||
t.Fatalf("仅 AI=0.9 归一化后风险分应为 90,实际 %.2f", assessment.Score)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAbnormalProbabilityIgnoresUnknown(t *testing.T) {
|
||||
if got := AbnormalProbability([]AIDetection{{ClassName: "unknown", Confidence: .9}}); got != 0 {
|
||||
t.Errorf("unknown 不应贡献异常概率,实际 %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeRiskScoreMissingAll(t *testing.T) {
|
||||
got := ComputeRiskScore(RiskInput{})
|
||||
if got.Score != 0 {
|
||||
t.Errorf("无任何组件时分数应为 0,实际 %.2f", got.Score)
|
||||
}
|
||||
if got.Confidence != "unknown" {
|
||||
t.Errorf("无 AI 组件时 confidence 应为 unknown,实际 %s", got.Confidence)
|
||||
}
|
||||
if len(got.Missing) != 4 {
|
||||
t.Errorf("missing 应为 4 项,实际 %v", got.Missing)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeRiskScoreClamps(t *testing.T) {
|
||||
if s := ComputeRiskScore(RiskInput{AI: 2, Env: 2, Stage: 2, Uniformity: 2}); s > 100 {
|
||||
t.Errorf("应钳制到 100,实际 %.2f", s)
|
||||
got := ComputeRiskScore(RiskInput{AI: ptr(2), Env: ptr(2), Stage: ptr(2), Uniformity: ptr(2)})
|
||||
if got.Score > 100 {
|
||||
t.Errorf("应钳制到 100,实际 %.2f", got.Score)
|
||||
}
|
||||
if s := ComputeRiskScore(RiskInput{AI: -1}); s < 0 {
|
||||
t.Errorf("应钳制到 0,实际 %.2f", s)
|
||||
if got := ComputeRiskScore(RiskInput{AI: ptr(-1)}).Score; got < 0 {
|
||||
t.Errorf("应钳制到 0,实际 %.2f", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRiskConfidenceLowMediumUnknown(t *testing.T) {
|
||||
if got := RiskConfidence(RiskInput{}); got != "unknown" {
|
||||
t.Errorf("无 AI 应为 unknown,实际 %s", got)
|
||||
}
|
||||
if got := RiskConfidence(RiskInput{AI: ptr(0.2)}); got != "low" {
|
||||
t.Errorf("低异常概率应为 low,实际 %s", got)
|
||||
}
|
||||
if got := RiskConfidence(RiskInput{AI: ptr(0.9)}); got != "medium" {
|
||||
t.Errorf("高异常概率当前最多应为 medium,实际 %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeRiskScoreBoundaryUsesWeightNormalization(t *testing.T) {
|
||||
// 0.5*0.8 + 0.2*0.5 的可用权重为 0.7:0.5/0.7 = 71.43
|
||||
got := ComputeRiskScore(RiskInput{AI: ptr(0.8), Env: ptr(0.5)})
|
||||
want := 50.0 / 0.7
|
||||
if math.Abs(got.Score-want) > 0.01 {
|
||||
t.Errorf("归一化应得 %.2f,实际 %.2f", want, got.Score)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRiskLevelBoundaries(t *testing.T) {
|
||||
cases := map[float64]string{
|
||||
0: "green",
|
||||
30: "green",
|
||||
0: "green",
|
||||
30: "green",
|
||||
30.5: "yellow",
|
||||
60: "yellow",
|
||||
61: "orange",
|
||||
80: "orange",
|
||||
81: "red",
|
||||
100: "red",
|
||||
60: "yellow",
|
||||
61: "orange",
|
||||
80: "orange",
|
||||
81: "red",
|
||||
100: "red",
|
||||
}
|
||||
for score, want := range cases {
|
||||
if got := RiskLevel(score); got != want {
|
||||
@@ -63,19 +140,19 @@ func TestStageCoefficientMapping(t *testing.T) {
|
||||
|
||||
func TestEnvCoefficientRules(t *testing.T) {
|
||||
// 湿度 >=80 → 高(真菌病)
|
||||
if c := EnvCoefficient(f(25), f(85)); c < 0.7 {
|
||||
if c := EnvCoefficient(ptr(25), ptr(85)); c < 0.7 {
|
||||
t.Errorf("湿度 85 应 ≥0.7,实际 %.2f", c)
|
||||
}
|
||||
// 湿度 75-80 → 中
|
||||
if c := EnvCoefficient(f(25), f(78)); c < 0.3 {
|
||||
if c := EnvCoefficient(ptr(25), ptr(78)); c < 0.3 {
|
||||
t.Errorf("湿度 78 应 ≥0.3,实际 %.2f", c)
|
||||
}
|
||||
// 温度突变 >30 → 中(核型多角体病诱发)
|
||||
if c := EnvCoefficient(f(32), f(60)); c < 0.3 {
|
||||
if c := EnvCoefficient(ptr(32), ptr(60)); c < 0.3 {
|
||||
t.Errorf("温度 32 应 ≥0.3,实际 %.2f", c)
|
||||
}
|
||||
// 舒适环境 → 0
|
||||
if c := EnvCoefficient(f(25), f(60)); c != 0 {
|
||||
if c := EnvCoefficient(ptr(25), ptr(60)); c != 0 {
|
||||
t.Errorf("舒适环境应为 0,实际 %.2f", c)
|
||||
}
|
||||
// 缺数据 → 0
|
||||
|
||||
Reference in New Issue
Block a user