package handler import ( "encoding/json" "net/http" "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/: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)) } 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) } }