package service import ( "math" "testing" ) func ptr(v float64) *float64 { return &v } func TestComputeRiskScoreWeights(t *testing.T) { 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) } if len(got.Missing) != 0 { t.Errorf("全组件可用时不应有 missing,实际 %v", got.Missing) } 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) { 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 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", 30.5: "yellow", 60: "yellow", 61: "orange", 80: "orange", 81: "red", 100: "red", } for score, want := range cases { if got := RiskLevel(score); got != want { t.Errorf("RiskLevel(%.1f) = %s, want %s", score, got, want) } } } func TestStageCoefficientMapping(t *testing.T) { if StageCoefficient("pupa") <= 0 { t.Error("蛹期应有风险系数") } if StageCoefficient("") != 0 { t.Error("空阶段应为 0") } if StageCoefficient("unknown") != 0 { t.Error("未知阶段应为 0") } if StageCoefficient("pupa") <= StageCoefficient("egg") { t.Error("蛹期系数应高于卵期") } } func TestEnvCoefficientRules(t *testing.T) { // 湿度 >=80 → 高(真菌病) if c := EnvCoefficient(ptr(25), ptr(85)); c < 0.7 { t.Errorf("湿度 85 应 ≥0.7,实际 %.2f", c) } // 湿度 75-80 → 中 if c := EnvCoefficient(ptr(25), ptr(78)); c < 0.3 { t.Errorf("湿度 78 应 ≥0.3,实际 %.2f", c) } // 温度突变 >30 → 中(核型多角体病诱发) if c := EnvCoefficient(ptr(32), ptr(60)); c < 0.3 { t.Errorf("温度 32 应 ≥0.3,实际 %.2f", c) } // 舒适环境 → 0 if c := EnvCoefficient(ptr(25), ptr(60)); c != 0 { t.Errorf("舒适环境应为 0,实际 %.2f", c) } // 缺数据 → 0 if c := EnvCoefficient(nil, nil); c != 0 { t.Errorf("无数据应为 0,实际 %.2f", c) } }