feat(server-go): 蚕房健康画像与年度发病统计(#24)

This commit is contained in:
weijuesen
2026-08-13 13:34:13 +08:00
parent 1a6bcd27fe
commit cbcb22e851
5 changed files with 252 additions and 6 deletions
@@ -0,0 +1,87 @@
package service
import (
"sort"
"strings"
)
// HealthInput 健康画像输入
type HealthInput struct {
RecentRiskLevels []string // 近 30 天巡检风险等级
LampPositive int
LampTotal int
ConsultationCount int
TraceCount int
}
// ComputeHealthScore 综合健康分(0-100)与评级(优/良/中/差)
func ComputeHealthScore(in HealthInput) (float64, string) {
score := 100.0
for _, level := range in.RecentRiskLevels {
switch level {
case "red":
score -= 25
case "orange":
score -= 12
case "yellow":
score -= 4
}
}
if in.LampTotal > 0 {
score -= float64(in.LampPositive) / float64(in.LampTotal) * 30
}
score -= float64(in.ConsultationCount) * 10
score -= float64(in.TraceCount) * 8
if score < 0 {
score = 0
}
if score > 100 {
score = 100
}
grade := "差"
switch {
case score >= 85:
grade = "优"
case score >= 70:
grade = "良"
case score >= 55:
grade = "中"
}
return score, grade
}
// MonthDiseaseEntry 月度发病条目
type MonthDiseaseEntry struct {
Month string
Disease string
}
// MonthStat 月度发病统计
type MonthStat struct {
Month string `json:"month"`
Total int `json:"total"`
Diseases map[string]int `json:"diseases"`
}
// AggregateMonthlyStats 按月份聚合发病数与病种分布(升序)
func AggregateMonthlyStats(entries []MonthDiseaseEntry) []MonthStat {
byMonth := make(map[string]*MonthStat)
for _, e := range entries {
if strings.TrimSpace(e.Month) == "" {
continue
}
s, ok := byMonth[e.Month]
if !ok {
s = &MonthStat{Month: e.Month, Diseases: map[string]int{}}
byMonth[e.Month] = s
}
s.Total++
s.Diseases[e.Disease]++
}
result := make([]MonthStat, 0, len(byMonth))
for _, s := range byMonth {
result = append(result, *s)
}
sort.Slice(result, func(i, j int) bool { return result[i].Month < result[j].Month })
return result
}
@@ -0,0 +1,52 @@
package service
import "testing"
func TestComputeHealthScore(t *testing.T) {
if s, g := ComputeHealthScore(HealthInput{}); s != 100 || g != "优" {
t.Errorf("空输入应 100/优,实际 %.1f/%s", s, g)
}
bad := HealthInput{
RecentRiskLevels: []string{"red", "red"},
LampPositive: 1,
LampTotal: 2,
ConsultationCount: 1,
TraceCount: 1,
}
s, g := ComputeHealthScore(bad)
if s >= 80 {
t.Errorf("风险输入应显著扣分,实际 %.1f", s)
}
if g == "优" {
t.Errorf("风险输入不应评级为优,实际 %s", g)
}
}
func TestComputeHealthScoreClamp(t *testing.T) {
s, _ := ComputeHealthScore(HealthInput{RecentRiskLevels: []string{"red", "red", "red", "red", "red"}})
if s < 0 {
t.Errorf("分数不应为负,实际 %.1f", s)
}
}
func TestAggregateMonthlyStats(t *testing.T) {
entries := []MonthDiseaseEntry{
{Month: "2026-07", Disease: "白僵病"},
{Month: "2026-07", Disease: "白僵病"},
{Month: "2026-07", Disease: "软化病"},
{Month: "2026-08", Disease: "白僵病"},
}
stats := AggregateMonthlyStats(entries)
if len(stats) != 2 {
t.Fatalf("月份数 = %d, want 2", len(stats))
}
if stats[0].Month != "2026-07" || stats[0].Total != 3 {
t.Errorf("2026-07 应 total=3,实际 %+v", stats[0])
}
if stats[0].Diseases["白僵病"] != 2 || stats[0].Diseases["软化病"] != 1 {
t.Errorf("2026-07 病种分布不正确: %+v", stats[0].Diseases)
}
if len(AggregateMonthlyStats(nil)) != 0 {
t.Error("空输入应返回空")
}
}