package handler import ( "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" ) // RegisterHealthRoutes 注册健康检查路由 func RegisterHealthRoutes(rg *gin.RouterGroup) { rg.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) } // RegisterHealthProfileRoutes 注册蚕房健康画像路由 func RegisterHealthProfileRoutes(rg *gin.RouterGroup, db *gorm.DB) { rg.GET("/health-profiles/:roomId", middleware.RequirePermission(db, "room:read"), roomHealthProfile(db)) rg.GET("/health-profiles/:roomId/effect", middleware.RequirePermission(db, "room:read"), roomEffectReport(db)) } // roomHealthProfile 单蚕房健康画像:巡检风险/检测结果/事件聚合 → 综合健康分 func roomHealthProfile(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { id := c.Param("roomId") if !canAccessRoom(db, c, &id) { c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该蚕房"}) return } var room model.Room if db.Where("id = ?", id).First(&room).Error != nil { c.JSON(http.StatusNotFound, gin.H{"error": "room not found"}) return } since := time.Now().Add(-30 * 24 * time.Hour) // 近 30 天巡检风险分布 var riskRows []struct { RiskLevel string `gorm:"column:risk_level"` Cnt int64 `gorm:"column:cnt"` } db.Table("inspection_records"). Select("risk_level, count(*) AS cnt"). Where("room_id = ? AND ai_status = 'done' AND risk_level IS NOT NULL AND created_at >= ? AND COALESCE(is_mock, false) = false", id, since). Group("risk_level"). Scan(&riskRows) riskCounts := map[string]int64{} var riskLevels []string for _, r := range riskRows { riskCounts[r.RiskLevel] = r.Cnt for i := int64(0); i < r.Cnt; i++ { riskLevels = append(riskLevels, r.RiskLevel) } } // 近 30 天 LAMP/分子检测结果 lampTotal, lampPositive := int64(0), int64(0) db.Model(&model.LampTest{}). Where("room_id = ? AND status = 'resulted' AND created_at >= ?", id, since). Count(&lampTotal) db.Model(&model.LampTest{}). Where("room_id = ? AND status = 'resulted' AND result = 'positive' AND created_at >= ?", id, since). Count(&lampPositive) // 事件数 var consultationCount, traceCount int64 db.Model(&model.Consultation{}).Where("room_id = ?", id).Count(&consultationCount) db.Model(&model.TraceRecord{}).Where("room_id = ?", id).Count(&traceCount) // 当前批次 var batch *model.Batch var latest model.Batch if db.Where("room_id = ?", id).Order("created_at DESC").First(&latest).Error == nil { batch = &latest } score, grade := service.ComputeHealthScore(service.HealthInput{ RecentRiskLevels: riskLevels, LampPositive: int(lampPositive), LampTotal: int(lampTotal), ConsultationCount: int(consultationCount), TraceCount: int(traceCount), }) c.JSON(http.StatusOK, gin.H{ "roomId": room.ID, "roomName": room.Name, "score": score, "grade": grade, "riskDistribution": gin.H{ "green": riskCounts["green"], "yellow": riskCounts["yellow"], "orange": riskCounts["orange"], "red": riskCounts["red"], }, "lampResults": gin.H{"total": lampTotal, "positive": lampPositive}, "events": gin.H{"consultations": consultationCount, "traces": traceCount}, "batch": batch, }) } } // roomEffectReport 处置前后窗口效果报告。 func roomEffectReport(db *gorm.DB) gin.HandlerFunc { return func(c *gin.Context) { roomID := c.Param("roomId") if !canAccessRoom(db, c, &roomID) { c.JSON(http.StatusForbidden, gin.H{"error": "无权访问该蚕房"}) return } now := time.Now() parse := func(key string, fallback time.Time) time.Time { raw := c.Query(key) if raw == "" { return fallback } t, err := time.Parse(time.RFC3339, raw) if err != nil { return fallback } return t } window := service.EffectWindow{ BeforeStart: parse("beforeStart", now.Add(-60*24*time.Hour)), BeforeEnd: parse("beforeEnd", now.Add(-30*24*time.Hour)), AfterStart: parse("afterStart", now.Add(-30*24*time.Hour)), AfterEnd: parse("afterEnd", now), } before, after := service.EffectMetrics{}, service.EffectMetrics{} loadEffectMetrics := func(start, end time.Time) service.EffectMetrics { metrics := service.EffectMetrics{} db.Table("inspection_records"). Where("room_id = ? AND ai_status = 'done' AND COALESCE(is_mock, false) = false AND risk_score IS NOT NULL AND created_at >= ? AND created_at < ?", roomID, start, end). Pluck("risk_score", &metrics.RiskSamples) var lampTotal int64 db.Model(&model.LampTest{}). Where("room_id = ? AND status = 'resulted' AND created_at >= ? AND created_at < ?", roomID, start, end). Count(&lampTotal) metrics.LampTotal = int(lampTotal) var lampPositive int64 db.Model(&model.LampTest{}). Where("room_id = ? AND status = 'resulted' AND result = 'positive' AND created_at >= ? AND created_at < ?", roomID, start, end). Count(&lampPositive) metrics.LampPositive = int(lampPositive) return metrics } before = loadEffectMetrics(window.BeforeStart, window.BeforeEnd) after = loadEffectMetrics(window.AfterStart, window.AfterEnd) var recurrences int64 db.Model(&model.DiseaseEvent{}). Where("room_id = ? AND status = 'reopened' AND created_at >= ? AND created_at < ?", roomID, window.AfterStart, window.AfterEnd). Count(&recurrences) after.Recurrences = int(recurrences) c.JSON(http.StatusOK, service.EvaluateControlEffect(roomID, window, before, after)) } }