diff --git a/src/modules/network_data/controller/ims.go b/src/modules/network_data/controller/ims.go index ffe7d3d1..d8824015 100644 --- a/src/modules/network_data/controller/ims.go +++ b/src/modules/network_data/controller/ims.go @@ -279,3 +279,60 @@ func (s IMSController) KPIBusyHour(c *gin.Context) { data := s.kpiReportService.IMSBusyHour(neInfo.RmUID, query.Timestamp) c.JSON(200, resp.OkData(data)) } + +// KPI 忙时统计 周 +// +// GET /kpi/busy-week +// +// @Tags network_data/ims +// @Accept json +// @Produce json +// @Param neId query string true "NE ID" default(001) +// @Param timestamp query int64 false "timestamp" +// @Success 200 {object} object "Response Results" +// @Security TokenAuth +// @Summary Busy week statistics +// @Description Busy week statistics +// @Router /neData/ims/kpi/busy-week [get] +func (s IMSController) KPIBusyWeek(c *gin.Context) { + language := reqctx.AcceptLanguage(c) + var query struct { + NeID string `form:"neId" binding:"required"` + WeekStart int64 `form:"weekStart" binding:"required"` // 时间戳毫秒 年月日 + WeekEnd int64 `form:"weekEnd" binding:"required"` // 时间戳毫秒 年月日 + } + if err := c.ShouldBindQuery(&query); err != nil { + errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err)) + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_PARSER, errMsgs)) + return + } + if query.WeekStart < 1e12 || query.WeekStart > 1e13 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekStart format is ms")) + return + } + if query.WeekEnd < 1e12 || query.WeekEnd > 1e13 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekEnd format is ms")) + return + } + if query.WeekEnd < query.WeekStart || query.WeekEnd == query.WeekStart { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekEnd must be greater than weekStart and not equal to weekStart")) + return + } + // 计算周差 + weekDiff := query.WeekEnd - query.WeekStart + // 周差是否7天 + if weekDiff-7*24*60*60*1000 != -1000 { + c.JSON(422, resp.CodeMsg(resp.CODE_PARAM_CHEACK, "weekEnd must be 7 days after weekStart")) + return + } + + // 查询网元获取IP + neInfo := s.neInfoService.FindByNeTypeAndNeID("IMS", query.NeID) + if neInfo.NeId != query.NeID || neInfo.IP == "" { + c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo"))) + return + } + + data := s.kpiReportService.IMSBusyWeek(neInfo.RmUID, query.WeekStart, query.WeekEnd) + c.JSON(200, resp.OkData(data)) +} diff --git a/src/modules/network_data/network_data.go b/src/modules/network_data/network_data.go index 523a0bef..622ff8f9 100644 --- a/src/modules/network_data/network_data.go +++ b/src/modules/network_data/network_data.go @@ -188,6 +188,10 @@ func Setup(router *gin.Engine) { middleware.AuthorizeUser(nil), controller.NewIMS.KPIBusyHour, ) + imsGroup.GET("/kpi/busy-week", + middleware.AuthorizeUser(nil), + controller.NewIMS.KPIBusyWeek, + ) } // 网元SMSC diff --git a/src/modules/network_data/service/kpi_report.go b/src/modules/network_data/service/kpi_report.go index 319d1ade..5e6c8210 100644 --- a/src/modules/network_data/service/kpi_report.go +++ b/src/modules/network_data/service/kpi_report.go @@ -349,3 +349,153 @@ func (r KpiReport) IMSBusyHour(rmUID string, timestamp int64) []map[string]any { } return data } + +// 定义结构体用于存储话务量值和对应的时间 +type TrafficData struct { + Time int64 `json:"time"` // 时间戳(毫秒) + Value float64 `json:"value"` // 话务量值 +} + +// IMSBusyWeek IMS忙时流量统计 周 +func (r KpiReport) IMSBusyWeek(rmUID string, weekStart, weekEnd int64) map[string]any { + weekStartTime := time.UnixMilli(weekStart) + weekEndTime := time.UnixMilli(weekEnd) + + // 1. 获取一周内每小时的呼叫数据 + // 转换为毫秒级时间戳 + rows := r.kpiReportRepository.SelectIMS(rmUID, weekStartTime.UnixMilli(), weekEndTime.UnixMilli()) + + // 创建一个map来存储按时间段合并后的数据 + timeGroup := make(map[int64]map[string]int64) + // 遍历每个数据项 + for _, row := range rows { + // 将毫秒时间戳转换为小时级时间戳(保留到小时的起始毫秒) + timeHour := row.CreatedAt / 3600000 * 3600000 // 1小时 = 3600000毫秒 + + // 解析 JSON 字符串为 map + var kpiValues []map[string]any + err := json.Unmarshal([]byte(row.KpiValues), &kpiValues) + if err != nil { + continue + } + + var callAttempts, callCompletions int64 + for _, v := range kpiValues { + if k, ok := v["kpiId"]; ok { + if k == "SCSCF.06" { + callAttempts = parse.Number(v["value"]) + } + if k == "SCSCF.09" { + callCompletions = parse.Number(v["value"]) + } + } + } + // 合并到对应的小时段 + if _, exists := timeGroup[timeHour]; !exists { + timeGroup[timeHour] = map[string]int64{ + "callAttempts": 0, + "callCompletions": 0, + } + } + timeGroup[timeHour]["callAttempts"] += callAttempts + timeGroup[timeHour]["callCompletions"] += callCompletions + } + + // 时间组合输出 + data := make([]map[string]any, 0, len(timeGroup)) + for hour, sums := range timeGroup { + data = append(data, map[string]any{ + "timeGroup": fmt.Sprintf("%d", hour), + "callAttempts": sums["callAttempts"], + "callCompletions": sums["callCompletions"], + }) + } + + if len(data) == 0 { + return map[string]any{ + "busyHourAverageBHCA": 0, + "busyHourAverageBHCC": 0, + "topFourHoursBHCA": []float64{}, + "topFourHoursBHCC": []float64{}, + "totalHours": 0, + } + } + + // 2. 分离BHCA和BHCC数据,并按降序排序 + var bhcaData []TrafficData + var bhccData []TrafficData + + for _, row := range data { + // 获取时间戳 + timeValue := int64(0) + if t, ok := row["timeGroup"]; ok { + timeValue = parse.Number(t) + } + + // 处理BHCA数据 + if value, ok := row["callAttempts"]; ok { + bhcaVal := parse.Number(value) + bhcaData = append(bhcaData, TrafficData{ + Time: timeValue, + Value: float64(bhcaVal), + }) + } + + // 处理BHCC数据 + if value, ok := row["callCompletions"]; ok { + bhccVal := parse.Number(value) + bhccData = append(bhccData, TrafficData{ + Time: timeValue, + Value: float64(bhccVal), + }) + } + } + // 按降序排序(值大的在前) + sort.Slice(bhcaData, func(i, j int) bool { return bhcaData[i].Value > bhcaData[j].Value }) + sort.Slice(bhccData, func(i, j int) bool { return bhccData[i].Value > bhccData[j].Value }) + + // 3. 取前四个最高值并计算平均值 + topFourBHCA := getTopFourTrafficData(bhcaData) + topFourBHCC := getTopFourTrafficData(bhccData) + + avgBHCA := calculateTrafficDataAverage(topFourBHCA) + avgBHCC := calculateTrafficDataAverage(topFourBHCC) + + // 4. 返回结果 + return map[string]any{ + "busyHourAverageBHCA": avgBHCA, + "busyHourAverageBHCC": avgBHCC, + "topFourHoursBHCA": topFourBHCA, + "topFourHoursBHCC": topFourBHCC, + "totalHours": len(data), + } +} + +// 辅助函数:获取前四个最高值的TrafficData +func getTopFourTrafficData(data []TrafficData) []TrafficData { + if len(data) == 0 { + return []TrafficData{} + } + + // 最多取前四个值 + maxCount := 4 + if len(data) < maxCount { + maxCount = len(data) + } + + return data[:maxCount] +} + +// 辅助函数:计算TrafficData的平均值 +func calculateTrafficDataAverage(data []TrafficData) float64 { + if len(data) == 0 { + return 0 + } + + var sum float64 = 0 + for _, v := range data { + sum += v.Value + } + + return sum / float64(len(data)) +}