185 lines
5.0 KiB
Go
185 lines
5.0 KiB
Go
package service
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// EffectWindow 处置前后评估窗口。
|
|
type EffectWindow struct {
|
|
BeforeStart time.Time `json:"beforeStart"`
|
|
BeforeEnd time.Time `json:"beforeEnd"`
|
|
AfterStart time.Time `json:"afterStart"`
|
|
AfterEnd time.Time `json:"afterEnd"`
|
|
}
|
|
|
|
// EffectMetrics 处置前后指标输入。
|
|
type EffectMetrics struct {
|
|
RiskSamples []float64 `json:"riskSamples"`
|
|
LampPositive int `json:"lampPositive"`
|
|
LampTotal int `json:"lampTotal"`
|
|
Recurrences int `json:"recurrences"`
|
|
Loss *float64 `json:"loss,omitempty"`
|
|
Cost *float64 `json:"cost,omitempty"`
|
|
}
|
|
|
|
// EffectReport 防控效果报告;缺失维度不按 0 当改善。
|
|
type EffectReport struct {
|
|
EventID string `json:"eventId"`
|
|
Window EffectWindow `json:"window"`
|
|
BeforeRiskAvg *float64 `json:"beforeRiskAvg,omitempty"`
|
|
AfterRiskAvg *float64 `json:"afterRiskAvg,omitempty"`
|
|
BeforePositiveRate *float64 `json:"beforePositiveRate,omitempty"`
|
|
AfterPositiveRate *float64 `json:"afterPositiveRate,omitempty"`
|
|
Recurrences int `json:"recurrences"`
|
|
Missing []string `json:"missing"`
|
|
Conclusion string `json:"conclusion"`
|
|
}
|
|
|
|
// EvaluateControlEffect 对比指定窗口风险、阳性率和复发;缺失维度单独列出。
|
|
func EvaluateControlEffect(eventID string, window EffectWindow, before, after EffectMetrics) EffectReport {
|
|
report := EffectReport{
|
|
EventID: eventID,
|
|
Window: window,
|
|
Recurrences: after.Recurrences,
|
|
Missing: []string{},
|
|
}
|
|
if len(before.RiskSamples) > 0 {
|
|
value := avg(before.RiskSamples)
|
|
report.BeforeRiskAvg = &value
|
|
} else {
|
|
report.Missing = append(report.Missing, "before_risk")
|
|
}
|
|
if len(after.RiskSamples) > 0 {
|
|
value := avg(after.RiskSamples)
|
|
report.AfterRiskAvg = &value
|
|
} else {
|
|
report.Missing = append(report.Missing, "after_risk")
|
|
}
|
|
if before.LampTotal > 0 {
|
|
value := float64(before.LampPositive) / float64(before.LampTotal)
|
|
report.BeforePositiveRate = &value
|
|
} else {
|
|
report.Missing = append(report.Missing, "before_lamp")
|
|
}
|
|
if after.LampTotal > 0 {
|
|
value := float64(after.LampPositive) / float64(after.LampTotal)
|
|
report.AfterPositiveRate = &value
|
|
} else {
|
|
report.Missing = append(report.Missing, "after_lamp")
|
|
}
|
|
if before.Loss == nil {
|
|
report.Missing = append(report.Missing, "before_loss")
|
|
}
|
|
if after.Loss == nil {
|
|
report.Missing = append(report.Missing, "after_loss")
|
|
}
|
|
if before.Cost == nil {
|
|
report.Missing = append(report.Missing, "before_cost")
|
|
}
|
|
if after.Cost == nil {
|
|
report.Missing = append(report.Missing, "after_cost")
|
|
}
|
|
switch {
|
|
case report.AfterRiskAvg == nil || report.BeforeRiskAvg == nil:
|
|
report.Conclusion = "证据不足,无法判断效果"
|
|
case *report.AfterRiskAvg < *report.BeforeRiskAvg && after.Recurrences == 0:
|
|
report.Conclusion = "改善"
|
|
case *report.AfterRiskAvg >= *report.BeforeRiskAvg:
|
|
report.Conclusion = "未改善"
|
|
default:
|
|
report.Conclusion = "需人工复核"
|
|
}
|
|
return report
|
|
}
|
|
|
|
func avg(values []float64) float64 {
|
|
sum := 0.0
|
|
for _, v := range values {
|
|
sum += v
|
|
}
|
|
return sum / float64(len(values))
|
|
}
|