package crontab import ( "fmt" "kpt-pasture/model" "math" "sort" "time" pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow" "gitee.com/xuyiping_admin/pkg/logger/zaplog" "go.uber.org/zap" ) func (e *Entry) UpdatePenBehavior() error { pastureList := e.FindPastureList() if pastureList == nil || len(pastureList) == 0 { return nil } for _, pasture := range pastureList { conf, err := e.GetSystemNeckRingConfigure(pasture.Id, model.MaxPenBehavior) if err != nil { zaplog.Error("UpdatePenBehavior", zap.Any("pasture", pasture), zap.Any("err", err)) continue } e.PenBehavior(pasture.Id, conf.Value) e.UpdatePenBehaviorWeekData(pasture.Id) } return nil } // PenBehavior 栏舍行为曲线 func (e *Entry) PenBehavior(pastureId, maxPenBehavior int64) { // 1. 获取颈环原始数据 penBehaviorModelList, err := e.getNeckRingOriginalList(pastureId, maxPenBehavior) if err != nil { zaplog.Error("PenBehavior", zap.Any("pastureId", pastureId), zap.Any("maxPenBehavior", maxPenBehavior), zap.Any("err", err), ) return } if len(penBehaviorModelList) <= 0 { return } // 2. 处理栏舍行为数据 penData := e.processPenBehaviorData(penBehaviorModelList) // 3. 计算平均值和百分比 e.calculateAveragesAndRates(penData) // 4. 保存数据 if err = e.savePenBehaviorData(penData); err != nil { zaplog.Error("PenBehavior", zap.Any("penData", penData), zap.Any("err", err)) return } sort.Slice(penBehaviorModelList, func(i, j int) bool { return penBehaviorModelList[i].Id > penBehaviorModelList[j].Id }) if err = e.UpdateSystemNeckRingConfigure(pastureId, model.MaxPenBehavior, penBehaviorModelList[0].Id); err != nil { zaplog.Error("PenBehavior", zap.Any("MaxPenBehavior", err), zap.Any("penBehaviorModelList", penBehaviorModelList)) } } // getNeckRingOriginalList 获取颈环原始数据 func (e *Entry) getNeckRingOriginalList(pastureId, maxPenBehavior int64) ([]*model.PenBehaviorModel, error) { var penBehaviorModelList []*model.PenBehaviorModel if err := e.DB.Table(fmt.Sprintf("%s as h", new(model.NeckRingOriginal).TableName())). Joins("JOIN cow as c ON h.pasture_id = c.pasture_id AND h.neck_ring_number = c.neck_ring_number"). Select("h.id,c.pasture_id, c.pen_id, c.pen_name, h.active_date, h.frameid, h.high, h.rumina, h.intake, h.inactive, h.gasp"). Where("h.id > ? AND h.pasture_id = ?", maxPenBehavior, pastureId). Order("h.active_date,h.frameid"). Limit(int(defaultLimit)). Find(&penBehaviorModelList).Error; err != nil { return nil, err } return penBehaviorModelList, nil } // processPenBehaviorData 处理栏舍行为数据 func (e *Entry) processPenBehaviorData(penBehaviorModelList []*model.PenBehaviorModel) map[string]*model.PenBehaviorData { // 按active_date和frameid分组 activeDateFrameIdMap := make(map[string][]*model.PenBehaviorModel) for _, v := range penBehaviorModelList { key := fmt.Sprintf("%s_%d", v.ActiveDate, v.Frameid) if activeDateFrameIdMap[key] == nil { activeDateFrameIdMap[key] = make([]*model.PenBehaviorModel, 0) } activeDateFrameIdMap[key] = append(activeDateFrameIdMap[key], v) } // 按pen_id分组统计 penData := make(map[string]*model.PenBehaviorData) for _, v := range activeDateFrameIdMap { // 按pen_id分组 penIdMap := make(map[int32]*model.PenBehaviorData) for _, item := range v { if data, exists := penIdMap[item.PenId]; exists { // 更新计数 data.CowCount++ // 更新平均值 data.AvgHigh += item.High // 更新行为统计 data.SumRumina += ifThenElse(item.Rumina >= 8, 1, 0) data.SumIntake += ifThenElse(item.Intake >= 8, 1, 0) data.SumRest += ifThenElse(item.Inactive >= 8, 1, 0) data.SumGasp += ifThenElse(item.Gasp >= 8, 1, 0) } else { penIdMap[item.PenId] = &model.PenBehaviorData{ PastureId: item.PastureId, PenId: item.PenId, PenName: item.PenName, HeatDate: item.ActiveDate, Frameid: item.Frameid, CowCount: 1, AvgHigh: item.High, SumRumina: ifThenElse(item.Rumina >= 8, 1, 0), SumIntake: ifThenElse(item.Intake >= 8, 1, 0), SumRest: ifThenElse(item.Inactive >= 8, 1, 0), SumGasp: ifThenElse(item.Gasp >= 8, 1, 0), } } } // 将penIdMap的数据合并到penData中 for penId, data := range penIdMap { key := fmt.Sprintf("%s_%d_%d", data.HeatDate, penId, data.Frameid) penData[key] = data } } return penData } // calculateAveragesAndRates 计算平均值和百分比 func (e *Entry) calculateAveragesAndRates(penData map[string]*model.PenBehaviorData) { for _, data := range penData { // 计算平均值 data.AvgHigh = data.AvgHigh / data.CowCount // 计算百分比 if data.CowCount > 0 { data.RuminaRate = int32(float64(data.SumRumina) / float64(data.CowCount) * 100) data.IntakeRate = int32(float64(data.SumIntake) / float64(data.CowCount) * 100) data.RestRate = int32(float64(data.SumRest) / float64(data.CowCount) * 100) data.GaspRate = int32(float64(data.SumGasp) / float64(data.CowCount) * 100) } } } // savePenBehaviorData 保存栏舍行为数据 func (e *Entry) savePenBehaviorData(penData map[string]*model.PenBehaviorData) error { for _, data := range penData { // 构建活动时间 activeTime := e.calculateActiveTime(data.HeatDate, data.Frameid) // 构建保存数据 penBehavior := model.NewPenBehavior(data, activeTime) if e.isExistByPenBehavior(data.PastureId, data.HeatDate, data.PenId, data.Frameid) { historyData := e.findPenBehavior(data.PastureId, data.HeatDate, data.PenId, data.Frameid) if historyData == nil || historyData.Id <= 0 { continue } // 计算新的总和和平均值 newCowCount := historyData.CowCount + penBehavior.CowCount newAvgHigh := (historyData.AvgHigh*historyData.CowCount + penBehavior.AvgHigh*penBehavior.CowCount) / newCowCount newSumRumina := historyData.SumRumina + penBehavior.SumRumina newSumIntake := historyData.SumIntake + penBehavior.SumIntake newSumRest := historyData.SumRest + penBehavior.SumRest newSumGasp := historyData.SumGasp + penBehavior.SumGasp if err := e.DB.Model(new(model.PenBehavior)). Where("id = ?", historyData.Id). Updates(map[string]interface{}{ "cow_count": newCowCount, "avg_high": newAvgHigh, "sum_rumina": newSumRumina, "sum_intake": newSumIntake, "sum_rest": newSumRest, "sum_gasp": newSumGasp, "rumina_rate": int32(float64(newSumRumina) / float64(newCowCount) * 100), "intake_rate": int32(float64(newSumIntake) / float64(newCowCount) * 100), "rest_rate": int32(float64(newSumRest) / float64(newCowCount) * 100), "gasp_rate": int32(float64(newSumGasp) / float64(newCowCount) * 100), }).Error; err != nil { zaplog.Error("savePenBehaviorData", zap.Any("penBehavior", penBehavior), zap.Any("err", err)) } continue } // 使用 Upsert 操作 if err := e.DB.Model(new(model.PenBehavior)). Create(penBehavior).Error; err != nil { zaplog.Error("savePenBehaviorData", zap.Any("penBehavior", penBehavior), zap.Any("err", err)) } } return nil } func (e *Entry) UpdatePenBehaviorWeekData(pastureId int64) { penBehaviorList := e.findWeekPenBehaviorList(pastureId) if len(penBehaviorList) == 0 { return } // 按日期和frameid排序 sort.Slice(penBehaviorList, func(i, j int) bool { if penBehaviorList[i].HeatDate == penBehaviorList[j].HeatDate { return penBehaviorList[i].Frameid < penBehaviorList[j].Frameid } return penBehaviorList[i].HeatDate < penBehaviorList[j].HeatDate }) // 处理每个日期和frameid的数据 for _, item := range penBehaviorList { currDate := item.HeatDate currFrameid := item.Frameid // 计算开始和结束日期 currTime, err := time.Parse(model.LayoutDate2, currDate) if err != nil { zaplog.Error("UpdatePenBehaviorWeekData", zap.Error(err)) continue } startTime := currTime.AddDate(0, 0, -7).Format(model.LayoutDate2) endTime := currTime.AddDate(0, 0, -1).Format(model.LayoutDate2) // 获取历史数据 historyList := e.findHistoryPenBehaviorList(pastureId, startTime, endTime, currFrameid) if len(historyList) == 0 { // 如果没有历史数据,将所有记录标记为-1 if err = e.DB.Model(new(model.PenBehavior)). Where("id = ?", item.Id). Updates(map[string]interface{}{ "week_rumina_rate": -1, "week_intake_rate": -1, "week_rest_rate": -1, "week_gasp_rate": -1, "is_show": pasturePb.IsShow_Ok, }).Error; err != nil { zaplog.Error("UpdatePenBehaviorWeekData", zap.Error(err)) } continue } // 按pen_id分组计算统计数据 penStats := make(map[int32]*model.PenBehaviorWeekModel) for _, v := range historyList { if stats, exists := penStats[v.PenId]; exists { stats.CowCount++ stats.SumRumina += v.RuminaRate stats.SumIntake += v.IntakeRate stats.SumRest += v.RestRate stats.SumGasp += v.GaspRate // 计算标准差 stats.RuminaRate = append(stats.RuminaRate, float64(v.RuminaRate)) stats.IntakeRate = append(stats.IntakeRate, float64(v.IntakeRate)) stats.RestRate = append(stats.RestRate, float64(v.RestRate)) stats.GaspRate = append(stats.GaspRate, float64(v.GaspRate)) } else { penStats[v.PenId] = &model.PenBehaviorWeekModel{ CowCount: 1, SumRumina: v.RuminaRate, SumIntake: v.IntakeRate, SumRest: v.RestRate, SumGasp: v.GaspRate, RuminaRate: []float64{float64(v.RuminaRate)}, IntakeRate: []float64{float64(v.IntakeRate)}, RestRate: []float64{float64(v.RestRate)}, GaspRate: []float64{float64(v.GaspRate)}, } } } // 更新当前记录 if stats, exists := penStats[item.PenId]; exists { if err = e.DB.Model(new(model.PenBehavior)). Where("id = ?", item.Id). Updates(map[string]interface{}{ "week_rumina_rate": int32(float64(stats.SumRumina) / float64(stats.CowCount)), "week_intake_rate": int32(float64(stats.SumIntake) / float64(stats.CowCount)), "week_rest_rate": int32(float64(stats.SumRest) / float64(stats.CowCount)), "week_gasp_rate": int32(float64(stats.SumGasp) / float64(stats.CowCount)), "rumina_std": int32(calculateStd(stats.RuminaRate)), "intake_std": int32(calculateStd(stats.IntakeRate)), "rest_std": int32(calculateStd(stats.RuminaRate)), "gasp_std": int32(calculateStd(stats.GaspRate)), "is_show": pasturePb.IsShow_Ok, }).Error; err != nil { zaplog.Error("UpdatePenBehaviorWeekData", zap.Error(err)) } } else { // 如果没有历史数据,标记为-1 if err = e.DB.Model(new(model.PenBehavior)). Where("id = ?", item.Id). Updates(map[string]interface{}{ "week_rumina_rate": -1, "week_intake_rate": -1, "week_rest_rate": -1, "week_gasp_rate": -1, "is_show": pasturePb.IsShow_Ok, }).Error; err != nil { zaplog.Error("UpdatePenBehaviorWeekData", zap.Error(err)) } } } } // findHistoryPenBehaviorList 获取历史数据 func (e *Entry) findHistoryPenBehaviorList(pastureId int64, startTime, endTime string, frameid int32) []*model.PenBehavior { res := make([]*model.PenBehavior, 0) if err := e.DB.Model(new(model.PenBehavior)). Where("pasture_id = ?", pastureId). Where("heat_date BETWEEN ? AND ?", startTime, endTime). Where("frameid = ?", frameid). Find(&res).Error; err != nil { zaplog.Error("findHistoryPenBehaviorList", zap.Error(err)) return nil } return res } // calculateStd 计算标准差 func calculateStd(values []float64) float64 { if len(values) == 0 { return 0 } // 计算平均值 var sum float64 for _, v := range values { sum += v } mean := sum / float64(len(values)) // 计算方差 var variance float64 for _, v := range values { diff := v - mean variance += diff * diff } variance /= float64(len(values)) // 返回标准差 return math.Sqrt(variance) } // calculateActiveTime 计算活动时间 func (e *Entry) calculateActiveTime(heatDate string, frameid int32) string { // 计算小时和分钟 hour := (frameid / 10) * 2 minute := (frameid%10)*20 - 1 if minute < 0 { minute = 0 } baseDate, err := time.Parse(model.LayoutDate2, heatDate) if err != nil { zaplog.Error("PenBehavior", zap.Any("calculateActiveTime", err)) return "" } baseTime := time.Date(baseDate.Year(), baseDate.Month(), baseDate.Day(), int(hour), 0, 0, 0, baseDate.Location()) finalTime := baseTime.Add(time.Duration(minute) * time.Minute) // 构建时间字符串 return finalTime.Format(model.LayoutTime) } // isExistByPenBehavior 是否存在 func (e *Entry) isExistByPenBehavior(pastureId int64, heatDate string, penId int32, frameid int32) bool { var count int64 if err := e.DB.Model(new(model.PenBehavior)). Where("pasture_id = ? AND heat_date = ? AND frameid = ? AND pen_id = ?", pastureId, heatDate, penId, frameid). Count(&count).Error; err != nil { return false } return count > 0 } func (e *Entry) findPenBehavior(pastureId int64, heatDate string, penId int32, frameid int32) *model.PenBehavior { res := &model.PenBehavior{} if err := e.DB.Model(new(model.PenBehavior)). Where("pasture_id = ? AND heat_date = ? AND frameid = ? AND pen_id = ?", pastureId, heatDate, penId, frameid). First(res).Error; err != nil { return nil } return res } func (e *Entry) findWeekPenBehaviorList(pastureId int64) []*model.PenBehavior { res := make([]*model.PenBehavior, 0) if err := e.DB.Model(new(model.PenBehavior)). Where("pasture_id = ?", pastureId). Where("is_show = ?", pasturePb.IsShow_No). Where("cow_count >= ?", model.PenBehaviorMinCowCount). Limit(int(defaultLimit)). Find(&res).Error; err != nil { return nil } return res } // ifThenElse 条件判断函数 func ifThenElse(condition bool, a, b int32) int32 { if condition { return a } return b }