feat(server-go): 疫病溯源三级模型与接口(#21,一级自动溯源/二级清单/三级备注)
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user