320 lines
9.8 KiB
Go
320 lines
9.8 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"silk-server-go/internal/middleware"
|
|
"silk-server-go/internal/model"
|
|
"silk-server-go/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// RegisterTraceRoutes 注册疫病溯源路由
|
|
func RegisterTraceRoutes(rg *gin.RouterGroup, db *gorm.DB) {
|
|
read := middleware.RequirePermission(db, "trace:read")
|
|
write := middleware.RequirePermission(db, "trace:write")
|
|
rg.GET("/trace-records", read, listTraceRecords(db))
|
|
rg.GET("/trace-records/region-stats", read, traceRegionStats(db))
|
|
rg.GET("/trace-records/:id", read, getTraceRecord(db))
|
|
rg.POST("/trace-records", write, createTraceRecord(db))
|
|
rg.PATCH("/trace-records/:id", write, updateTraceRecord(db))
|
|
rg.DELETE("/trace-records/:id", write, deleteTraceRecord(db))
|
|
rg.POST("/trace-records/:id/auto", write, autoTrace(db))
|
|
rg.GET("/trace-records/:id/checklist", read, getTraceChecklist(db))
|
|
rg.POST("/trace-records/:id/checklist", write, submitTraceChecklist(db))
|
|
}
|
|
|
|
// traceRegionStats 区域发病统计(近 N 天,按蚕房 region 聚合)
|
|
func traceRegionStats(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
days := 90
|
|
if d, err := strconv.Atoi(c.DefaultQuery("days", "90")); err == nil && d > 0 && d <= 365 {
|
|
days = d
|
|
}
|
|
q := db.Table("trace_records").
|
|
Select("rooms.region AS region, trace_records.disease AS disease").
|
|
Joins("LEFT JOIN rooms ON rooms.id = trace_records.room_id").
|
|
Where("trace_records.created_at >= ?", time.Now().Add(-time.Duration(days)*24*time.Hour))
|
|
if disease := c.Query("disease"); disease != "" {
|
|
q = q.Where("trace_records.disease = ?", disease)
|
|
}
|
|
var rows []struct {
|
|
Region string `gorm:"column:region"`
|
|
Disease string `gorm:"column:disease"`
|
|
}
|
|
q.Scan(&rows)
|
|
entries := make([]service.RegionDiseaseEntry, 0, len(rows))
|
|
for _, r := range rows {
|
|
entries = append(entries, service.RegionDiseaseEntry{Region: r.Region, Disease: r.Disease})
|
|
}
|
|
c.JSON(http.StatusOK, service.AggregateRegionStats(entries))
|
|
}
|
|
}
|
|
|
|
func listTraceRecords(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
q := db.Model(&model.TraceRecord{})
|
|
if status := c.Query("status"); status != "" {
|
|
q = q.Where("status = ?", status)
|
|
}
|
|
if room := c.Query("roomId"); room != "" {
|
|
q = q.Where("room_id = ?", room)
|
|
}
|
|
var list []model.TraceRecord
|
|
q.Order("created_at DESC").Find(&list)
|
|
fillTraceRoomNames(db, list)
|
|
c.JSON(http.StatusOK, list)
|
|
}
|
|
}
|
|
|
|
func fillTraceRoomNames(db *gorm.DB, list []model.TraceRecord) {
|
|
var rooms []model.Room
|
|
db.Select("id", "name").Find(&rooms)
|
|
names := make(map[string]string, len(rooms))
|
|
for _, r := range rooms {
|
|
names[r.ID] = r.Name
|
|
}
|
|
for i := range list {
|
|
if list[i].RoomID != nil {
|
|
if n, ok := names[*list[i].RoomID]; ok {
|
|
list[i].RoomName = &n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getTraceRecord(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var t model.TraceRecord
|
|
if db.Where("id = ?", c.Param("id")).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "trace record not found"})
|
|
return
|
|
}
|
|
fillTraceRoomNames(db, []model.TraceRecord{t})
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|
|
|
|
func createTraceRecord(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var body struct {
|
|
RoomID *string `json:"roomId"`
|
|
LampTestID *string `json:"lampTestId"`
|
|
ConsultationID *string `json:"consultationId"`
|
|
Disease string `json:"disease"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
for _, id := range []*string{body.RoomID, body.LampTestID, body.ConsultationID} {
|
|
if id != nil && *id != "" && !isUUID(*id) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "关联 ID 不是合法的 UUID"})
|
|
return
|
|
}
|
|
}
|
|
if body.Disease == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "病种不能为空"})
|
|
return
|
|
}
|
|
rec := model.TraceRecord{
|
|
RoomID: body.RoomID, LampTestID: body.LampTestID,
|
|
ConsultationID: body.ConsultationID, Disease: body.Disease, Status: "pending",
|
|
}
|
|
if err := db.Create(&rec).Error; err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "创建失败"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, rec)
|
|
}
|
|
}
|
|
|
|
func updateTraceRecord(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var t model.TraceRecord
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "trace record not found"})
|
|
return
|
|
}
|
|
updates, err := bindUpdates(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if len(updates) > 0 {
|
|
db.Model(&model.TraceRecord{}).Where("id = ?", id).Updates(updates)
|
|
}
|
|
db.Where("id = ?", id).First(&t)
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|
|
|
|
func deleteTraceRecord(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var t model.TraceRecord
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "trace record not found"})
|
|
return
|
|
}
|
|
db.Where("id = ?", id).Delete(&model.TraceRecord{})
|
|
c.JSON(http.StatusOK, gin.H{"id": id})
|
|
}
|
|
}
|
|
|
|
// autoTrace 一级自动溯源:环境回溯 + 历史关联 + 传播推断 → 溯源初报
|
|
func autoTrace(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var t model.TraceRecord
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "trace record not found"})
|
|
return
|
|
}
|
|
report := map[string]interface{}{
|
|
"disease": t.Disease,
|
|
"generatedAt": time.Now(),
|
|
}
|
|
origin := "unknown"
|
|
confidence := 0.4
|
|
|
|
// 环境回溯(房间最近温湿度)
|
|
var samples []service.EnvSample
|
|
if t.RoomID != nil {
|
|
var room model.Room
|
|
if db.Where("id = ?", *t.RoomID).First(&room).Error == nil {
|
|
report["roomName"] = room.Name
|
|
}
|
|
type row struct {
|
|
Metric string `gorm:"column:metric"`
|
|
Value float64 `gorm:"column:value"`
|
|
}
|
|
var rows []row
|
|
db.Table("telemetry").
|
|
Select("telemetry.metric, telemetry.value").
|
|
Joins("JOIN devices ON devices.device_key = telemetry.device_key AND devices.room_id = ?", *t.RoomID).
|
|
Where("telemetry.metric IN ?", []string{"humidity", "temperature"}).
|
|
Order("telemetry.timestamp DESC").
|
|
Limit(40).
|
|
Scan(&rows)
|
|
for _, r := range rows {
|
|
s := service.EnvSample{}
|
|
if r.Metric == "humidity" {
|
|
s.Humidity = &r.Value
|
|
} else {
|
|
s.Temp = &r.Value
|
|
}
|
|
samples = append(samples, s)
|
|
}
|
|
}
|
|
env := service.EnvironmentBacktrack(samples, 80)
|
|
report["environment"] = env
|
|
|
|
// 历史发病关联(同房间同病种 LAMP 记录)
|
|
pastCount := int64(0)
|
|
var past []service.PastEvent
|
|
if t.RoomID != nil {
|
|
raw, _ := json.Marshal([]string{t.Disease})
|
|
db.Model(&model.LampTest{}).
|
|
Where("room_id = ? AND status = 'resulted' AND diseases @> ?", *t.RoomID, string(raw)).
|
|
Count(&pastCount)
|
|
}
|
|
for i := int64(0); i < pastCount && i < 5; i++ {
|
|
past = append(past, service.PastEvent{Disease: t.Disease, DaysAgo: 15})
|
|
}
|
|
continuous := service.HistoryAssociation(past, t.Disease)
|
|
report["history"] = map[string]interface{}{
|
|
"continuous": continuous,
|
|
"pastCount": pastCount,
|
|
}
|
|
|
|
// 传播途径推断
|
|
mode, source := service.TransmissionInference(t.Disease)
|
|
report["transmission"] = map[string]interface{}{"mode": mode, "source": source}
|
|
|
|
// 初步来源判定
|
|
if continuous {
|
|
origin = "internal"
|
|
confidence = 0.7
|
|
} else if env.HighHumidity || env.TempSwing {
|
|
origin = "internal"
|
|
confidence = 0.6
|
|
}
|
|
report["origin"] = origin
|
|
report["confidence"] = confidence
|
|
|
|
raw, _ := json.Marshal(report)
|
|
db.Model(&model.TraceRecord{}).Where("id = ?", id).Updates(map[string]interface{}{
|
|
"auto_report": raw, "origin": origin, "confidence": confidence, "status": "reported",
|
|
})
|
|
db.Where("id = ?", id).First(&t)
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|
|
|
|
// getTraceChecklist 分病种二级排查清单
|
|
func getTraceChecklist(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var t model.TraceRecord
|
|
if db.Where("id = ?", c.Param("id")).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "trace record not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, service.DiseaseChecklist(t.Disease))
|
|
}
|
|
}
|
|
|
|
// submitTraceChecklist 二级:提交排查清单 → 溯源分析报告
|
|
func submitTraceChecklist(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var t model.TraceRecord
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "trace record not found"})
|
|
return
|
|
}
|
|
var body struct {
|
|
Answers []struct {
|
|
Key string `json:"key"`
|
|
Answer string `json:"answer"`
|
|
} `json:"answers"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
template := service.DiseaseChecklist(t.Disease)
|
|
byKey := make(map[string]service.ChecklistItem, len(template))
|
|
for _, item := range template {
|
|
byKey[item.Key] = item
|
|
}
|
|
var answers []service.ChecklistAnswer
|
|
for _, a := range body.Answers {
|
|
item, ok := byKey[a.Key]
|
|
if !ok {
|
|
continue
|
|
}
|
|
answers = append(answers, service.ChecklistAnswer{
|
|
Key: item.Key, Label: item.Label, Answer: a.Answer, InternalBias: item.InternalBias,
|
|
})
|
|
}
|
|
origin, confidence, conclusion := service.AnalyzeChecklist(answers)
|
|
report := map[string]interface{}{
|
|
"origin": origin, "confidence": confidence, "conclusion": conclusion,
|
|
"answers": answers, "generatedAt": time.Now(),
|
|
}
|
|
raw, _ := json.Marshal(report)
|
|
db.Model(&model.TraceRecord{}).Where("id = ?", id).Updates(map[string]interface{}{
|
|
"analysis_report": raw, "origin": origin, "confidence": confidence, "status": "analysis",
|
|
})
|
|
db.Where("id = ?", id).First(&t)
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|