package crontab import ( "kpt-pasture/model" "time" pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow" "gitee.com/xuyiping_admin/pkg/logger/zaplog" "go.uber.org/zap" "gitee.com/xuyiping_admin/pkg/xerr" ) const ( MaxRuminaAdJust = 20 XAdjust21 = 15 XAdjust42 = 10 RumtoHeat = 0.5 MinCalvingAge = 20 MinLact = 0 NormalChangJust = 10 B48 = 48 ) func (e *Entry) UpdateCowEstrus() (err error) { pastureList := e.FindPastureList() if pastureList == nil || len(pastureList) == 0 { return nil } for _, pasture := range pastureList { if err = e.EntryCowEstrus(pasture.Id); err != nil { zaplog.Error("EntryCrontab", zap.Any("PastureUpdateCowEstrus", err), zap.Any("pasture", pasture)) } zaplog.Info("PastureUpdateCowEstrus-success", zap.Any("pasture", pasture.Id)) } return nil } func (e *Entry) EntryCowEstrus(pastureId int64) (err error) { xToday := &XToday{} systemConfigureList, err := e.GetSystemConfigure(pastureId) for _, v := range systemConfigureList { switch v.Name { case model.ActiveLow: xToday.ActiveLow = int32(v.Value) case model.ActiveMiddle: xToday.ActiveMiddle = int32(v.Value) case model.ActiveHigh: xToday.ActiveHigh = int32(v.Value) } } if err = e.CowEstrusWarning(pastureId, xToday); err != nil { zaplog.Error("EntryCowEstrus", zap.Any("CowEstrusWarning", err), zap.Any("xToday", xToday)) } // 将历史发情预警数据更新为已过期 if err = e.DB.Model(new(model.NeckRingEstrus)). Where("estrus_start_date <= ?", time.Now().AddDate(0, 0, -4).Format(model.LayoutTime)). Update("is_show", pasturePb.IsShow_No).Error; err != nil { zaplog.Error("EntryCowEstrus", zap.Any("UpdateEventEstrus", err)) } return nil } // CowEstrusWarning 发情预警 func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) { nowTime := time.Now() neckActiveHabitList := make([]*model.NeckActiveHabit, 0) // todo 需要考虑到数据量太大的情况 if err = e.DB.Model(new(model.NeckActiveHabit)). Where("heat_date = ?", nowTime.Format(model.LayoutDate2)). Where("pasture_id = ?", pastureId). Where("filter_high > 0 AND change_filter > ?", model.DefaultChangeFilter). Where("cow_id > ?", 0). Where(e.DB.Where("calving_age > ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛 Find(&neckActiveHabitList).Error; err != nil { return xerr.WithStack(err) } zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitList", neckActiveHabitList)) neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit) for _, habit := range neckActiveHabitList { cft := calculateCFT(habit) if cft < float32(xToday.ActiveLow-XAdjust21) { continue } if _, ok := neckActiveHabitMap[habit.CowId]; !ok { neckActiveHabitMap[habit.CowId] = make([]*model.NeckActiveHabit, 0) } neckActiveHabitMap[habit.CowId] = append(neckActiveHabitMap[habit.CowId], habit) } zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitMap", neckActiveHabitMap)) neckRingEstrusList := make([]*model.NeckRingEstrus, 0) for cowId, cowHabitList := range neckActiveHabitMap { // 最近3天最大发情记录,小于该变化趋势的不再插入 before3Data := e.GetBeforeThreeDaysCowEstrus(cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime)) // 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42 cowEstrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime)) activeDateTime, _ := time.Parse(model.LayoutTime, cowEstrus.ActiveDate) if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() { cowEstrus.HadJust = XAdjust21 } if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -50).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -36).Unix() { cowEstrus.HadJust = XAdjust42 } maxCft := float32(0) maxHigh := int32(0) for _, habit := range cowHabitList { cft := calculateCFT(habit) if cft > maxCft { maxCft = cft } if habit.FilterHigh > maxHigh { maxHigh = habit.FilterHigh } } activeDate := "" if len(cowHabitList) > 0 { sortHabits := sortHabitsByChangeFilter(cowHabitList) activeDate = sortHabits[0].ActiveTime } b48 := float64(0) t1, _ := time.Parse(model.LayoutTime, activeDate) t3, err := time.Parse(model.LayoutTime, before3Data.ActiveDate) if err == nil { b48 = t3.Sub(t1).Hours() } if (int32(maxCft) > before3Data.DayHigh || b48 > B48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow { level := calculateLevel(maxCft, cowEstrus, xToday) cowInfo := e.FindCowInfoByNeckRingNumber(cowHabitList[0].NeckRingNumber) isShow := pasturePb.IsShow_Ok if cowInfo.IsPregnant == pasturePb.IsShow_Ok && level == pasturePb.EstrusLevel_Low { isShow = pasturePb.IsShow_No } dayHigh := int32(maxCft) + cowEstrus.HadJust lastEstrusDate := cowEstrus.ActiveDate checkResult := getResult(before3Data, maxCft, cowEstrus) isPeak := pasturePb.IsShow_Ok zaplog.Info("CowEstrusWarning", zap.Any("level", level), zap.Any("checkResult", checkResult), zap.Any("isShow", isShow), zap.Any("isPeak", isPeak), zap.Any("lastEstrusDate", lastEstrusDate), zap.Any("activeDate", activeDate), zap.Any("dayHigh", dayHigh), zap.Any("cft", maxCft), zap.Any("before3Data", before3Data), zap.Any("cowEstrus", cowEstrus), zap.Any("cowInfo", cowInfo), zap.Any("cowHabitList", cowHabitList), ) newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, pasturePb.ExposeEstrusType_Neck_Ring, level, checkResult, isShow) newNeckRingEstrus.LastEstrusDate = lastEstrusDate newNeckRingEstrus.ActiveDate = activeDate newNeckRingEstrus.DayHigh = dayHigh newNeckRingEstrus.MaxHigh = maxHigh newNeckRingEstrus.IsPeak = isPeak neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus) } } zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList)) if len(neckRingEstrusList) > 0 { if err = e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil { zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err)) } } if err = e.UpdateEstrusStartDate(pastureId, nowTime); err != nil { zaplog.Error("UpdateEstrusStartDate", zap.Any("err", err)) } return err } // UpdateEstrusStartDate 更新发情开始时间数据 func (e *Entry) UpdateEstrusStartDate(pastureId int64, xToday time.Time) (err error) { beforeEventEstrus := make([]*EstrusStartData, 0) if err = e.DB.Model(new(model.NeckRingEstrus)). Select("cow_id,MIN(estrus_start_date) as estrus_start_date"). Where("active_date BETWEEN ? AND ?", xToday.Add(-24*time.Hour).Format(model.LayoutTime), xToday.Format(model.LayoutTime)). Where("estrus_start_date != ?", ""). Where("expose_estrus_type = ?", pasturePb.ExposeEstrusType_Neck_Ring). Where("pasture_id = ?", pastureId). Group("cow_id").Find(&beforeEventEstrus).Error; err != nil { return xerr.WithStack(err) } if len(beforeEventEstrus) > 0 { for _, v := range beforeEventEstrus { if err = e.DB.Model(new(model.NeckRingEstrus)). Where("cow_id = ?", v.CowId). Where("active_date >= ? AND <= ?", xToday.Add(-1*time.Hour).Format(model.LayoutTime), xToday.Add(24*time.Hour).Format(model.LayoutTime)). Update("estrus_start_date", v.EstrusStartDate).Error; err != nil { zaplog.Error("UpdateEstrusStartDate", zap.Any("err", err)) } } } return nil } // UpdateIsPeak 更新IsPeak是否是高峰字段 1 是 0 否 func (e *Entry) UpdateIsPeak(pastureId int64, xToday time.Time) (err error) { return nil } // calculateCFT 计算cft值 func calculateCFT(habit *model.NeckActiveHabit) (cft float32) { cft = float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3 if habit.ChangeAdjust < 10 { cft = float32(habit.ChangeFilter) } cft = cft * float32(habit.FilterCorrect) / 100 ruminaAdjust := float32(0) switch { case habit.RuminaFilter > MaxRuminaAdJust: ruminaAdjust = float32(5) case habit.RuminaFilter > 0: ruminaAdjust = float32(habit.RuminaFilter) * 0.25 case habit.RuminaFilter < -MaxRuminaAdJust: ruminaAdjust = -MaxRuminaAdJust * RumtoHeat default: ruminaAdjust = float32(habit.RuminaFilter) * RumtoHeat } cft -= ruminaAdjust return cft } // calculateLevel 计算发情等级 func calculateLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind { level := pasturePb.EstrusLevel_High if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle { level = pasturePb.EstrusLevel_Low } if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh { level = pasturePb.EstrusLevel_Middle } return level } // getResult 根据b3数据计算结果 0 1 2 3 -1 -2 func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind { result := pasturePb.CheckResult_Invalid if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust { result = pasturePb.CheckResult_Fail } if b3.CheckResult == pasturePb.CheckResult_Overdue { result = pasturePb.CheckResult_Correct } return result } // sortHabitsByChangeFilter 根据change_filter排序 func sortHabitsByChangeFilter(habits []*model.NeckActiveHabit) []*model.NeckActiveHabit { sorted := make([]*model.NeckActiveHabit, len(habits)) copy(sorted, habits) for i := range sorted { for j := i + 1; j < len(sorted); j++ { if sorted[i].ChangeFilter < sorted[j].ChangeFilter { sorted[i], sorted[j] = sorted[j], sorted[i] } } } return sorted }