diff --git a/controllers/statistics.go b/controllers/statistics.go index 8e5dc49..1404af5 100644 --- a/controllers/statistics.go +++ b/controllers/statistics.go @@ -5,6 +5,7 @@ import ( "hr_receiver/config" "hr_receiver/models" "net/http" + "sort" "strconv" "strings" "time" @@ -334,3 +335,125 @@ func (sc *StatisticsController) StatisticsByRegion(c *gin.Context) { "regions": regions, }) } + +func (sc *StatisticsController) TimelineStatistics(c *gin.Context) { + regionIDStr := c.Query("regionId") + startTimeStr := c.Query("startTime") + endTimeStr := c.Query("endTime") + + query := sc.DB.Model(&models.AIAnalysisRecord{}) + if regionIDStr != "" { + if regionID, err := strconv.ParseUint(regionIDStr, 10, 32); err == nil { + query = query.Where("region_id = ?", uint32(regionID)) + } + } + if startTimeStr != "" { + if startTime, err := strconv.ParseInt(startTimeStr, 10, 64); err == nil { + query = query.Where("upload_time >= ?", startTime) + } + } + if endTimeStr != "" { + if endTime, err := strconv.ParseInt(endTimeStr, 10, 64); err == nil { + query = query.Where("upload_time <= ?", endTime) + } + } + + type timelineItem struct { + Date string `json:"date"` + Count int64 `json:"count"` + InputTokens int64 `json:"inputTokens"` + OutputTokens int64 `json:"outputTokens"` + } + + type rawRegionTimeline struct { + RegionID *uint32 + Date string + Count int64 + InputTokens int64 + OutputTokens int64 + } + + var rawResults []rawRegionTimeline + err := query.Select(` + region_id, + DATE(TO_TIMESTAMP(upload_time / 1000.0)) as date, + COUNT(*) as count, + COALESCE(SUM(input_tokens), 0) as input_tokens, + COALESCE(SUM(output_tokens), 0) as output_tokens + `).Group("region_id, DATE(TO_TIMESTAMP(upload_time / 1000.0))").Order("region_id, date ASC").Scan(&rawResults).Error + + if err != nil { + writeError(c, http.StatusInternalServerError, "failed to query timeline statistics") + return + } + + overallMap := make(map[string]*timelineItem) + regionItemsMap := make(map[string][]timelineItem) + regionIDs := make([]uint32, 0) + regionIDSet := make(map[uint32]struct{}) + + for _, r := range rawResults { + if overallMap[r.Date] == nil { + overallMap[r.Date] = &timelineItem{Date: r.Date} + } + overallMap[r.Date].Count += r.Count + overallMap[r.Date].InputTokens += r.InputTokens + overallMap[r.Date].OutputTokens += r.OutputTokens + + regionID := uint32(0) + if r.RegionID != nil { + regionID = *r.RegionID + } + regionIDStr := strconv.FormatUint(uint64(regionID), 10) + regionItemsMap[regionIDStr] = append(regionItemsMap[regionIDStr], timelineItem{ + Date: r.Date, + Count: r.Count, + InputTokens: r.InputTokens, + OutputTokens: r.OutputTokens, + }) + if _, ok := regionIDSet[regionID]; !ok && regionID > 0 { + regionIDSet[regionID] = struct{}{} + regionIDs = append(regionIDs, regionID) + } + } + + // 查询幼儿园名称 + kindergartenMap := make(map[uint32]string) + if len(regionIDs) > 0 { + var kindergartens []models.Kindergarten + if err := sc.DB.Where("region_id IN ?", regionIDs).Find(&kindergartens).Error; err == nil { + for _, k := range kindergartens { + kindergartenMap[k.RegionID] = k.Name + } + } + } + + type regionTimeline struct { + Name string `json:"name"` + Items []timelineItem `json:"items"` + } + regionsMap := make(map[string]regionTimeline) + for regionIDStr, items := range regionItemsMap { + name := "" + if regionID, err := strconv.ParseUint(regionIDStr, 10, 32); err == nil && regionID > 0 { + name = kindergartenMap[uint32(regionID)] + } + regionsMap[regionIDStr] = regionTimeline{ + Name: name, + Items: items, + } + } + + var overall []timelineItem + for _, item := range overallMap { + overall = append(overall, *item) + } + sort.Slice(overall, func(i, j int) bool { + return overall[i].Date < overall[j].Date + }) + + writeSuccess(c, http.StatusOK, "query success", gin.H{ + "overall": overall, + "regions": regionsMap, + }) +} diff --git a/routes/routes.go b/routes/routes.go index 0d4842d..dd3ecbf 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -90,6 +90,7 @@ func SetupRouter() *gin.Engine { admin.GET("/statistics/ai-analysis-records", statisticsController.ListAIAnalysisRecords) admin.DELETE("/statistics/ai-analysis-records/:id", statisticsController.DeleteAIAnalysisRecord) admin.GET("/statistics/ai-analysis", statisticsController.StatisticsByRegion) + admin.GET("/statistics/ai-analysis-timeline", statisticsController.TimelineStatistics) } v1.GET("/admin/system-debug/mqtt/ws", systemDebugController.MqttWebSocket)