package crontab import ( "fmt" "kpt-pasture/model" "kpt-pasture/util" "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.GetSystemNeckRingConfigure(pastureId) if err != nil { return xerr.WithStack(err) } if systemConfigureList == nil || len(systemConfigureList) == 0 { return nil } 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) } } nowTime := time.Now() e.CowEstrusWarning(pastureId, xToday, nowTime) e.UpdateNewNeckRingEstrus(pastureId, nowTime) return nil } // CowEstrusWarning 发情预警 func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.Time) { 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天内的发情牛 Order("cow_id"). Find(&neckActiveHabitList).Error; err != nil { zaplog.Error("CowEstrusWarning", zap.Any("Find", err)) return } 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 } zaplog.Info("CowEstrusWarning", zap.Any("cft", cft), zap.Any("habit", habit)) if neckActiveHabitMap[habit.CowId] == nil { 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, _ := util.TimeParseLocal(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) lastActiveDate := time.Time{} for _, habit := range cowHabitList { cft := calculateCFT(habit) if cft > maxCft { maxCft = cft } if habit.FilterHigh > maxHigh { maxHigh = habit.FilterHigh } // 获取最新的 CreateTime activeTimeParse, _ := util.TimeParseLocal(model.LayoutTime, habit.ActiveTime) if activeTimeParse.After(lastActiveDate) { lastActiveDate = activeTimeParse } } b48 := float64(0) t3, _ := util.TimeParseLocal(model.LayoutTime, before3Data.ActiveTime) b48 = t3.Sub(lastActiveDate).Hours() if (int32(maxCft) > before3Data.DayHigh || before3Data.CowId == 0 || b48 > B48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow { level := calculateActiveLevel(maxCft, cowEstrus, xToday) cowInfo := e.FindCowInfoByCowId(cowId) if cowInfo == nil { zaplog.Error("CowEstrusWarning", zap.Any("FindCowInfoByCowId", cowId)) continue } isShow := pasturePb.IsShow_Ok if cowInfo != nil && 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("b48", b48), zap.Any("checkResult", checkResult), zap.Any("isShow", isShow), zap.Any("isPeak", isPeak), zap.Any("lastEstrusDate", lastEstrusDate), zap.Any("activeDate", lastActiveDate), 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, level, checkResult, isShow) newNeckRingEstrus.LastTime = lastEstrusDate newNeckRingEstrus.ActiveTime = lastActiveDate.Format(model.LayoutTime) 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)) } } } func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, nowTime time.Time) { // 更新牛只首次发情时间 e.UpdateEstrusFirstTime1(pastureId, nowTime) e.UpdateEstrusIsPeak(pastureId, nowTime) e.UpdateEstrusFirstTime2(pastureId, nowTime) e.UpdateEstrusFirstTime3(pastureId, nowTime) } // UpdateEstrusFirstTime1 更新牛只首次发情时间 func (e *Entry) UpdateEstrusFirstTime1(pastureId int64, xToday time.Time) { neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId) for _, v := range neckRingEstrusList { cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v.CowId, xToday) if cowEstrusStartData != nil && cowEstrusStartData.FirstTime != "" { if err := e.DB.Model(new(model.NeckRingEstrus)). Where("id = ?", v.Id). Update("first_time", cowEstrusStartData.FirstTime).Error; err != nil { zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err), zap.Any("cowEstrusStartData", cowEstrusStartData), ) } } } } func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday time.Time) { neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId) for _, v := range neckRingEstrusList { zaplog.Info("UpdateEstrusFirstTime2", zap.Any("v", v)) } } func (e *Entry) UpdateEstrusFirstTime3(pastureId int64, xToday time.Time) { neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId) for _, v := range neckRingEstrusList { activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime) if activeTime.After(xToday.AddDate(0, 0, -2)) { if err := e.DB.Model(new(model.NeckRingEstrus)). Where("id = ?", v.Id). Update("first_time", v.ActiveTime).Error; err != nil { zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err)) } } } } func (e *Entry) UpdateEstrusIsPeak(pastureId int64, xToday time.Time) { neckRingEstrusList := make([]*model.NeckRingEstrus, 0) if err := e.DB.Model(new(model.NeckRingEstrus)). Where("first_time != ?", ""). Where("is_peak = ?", pasturePb.IsShow_Ok). Where("is_show = ?", pasturePb.IsShow_Ok). Where("pasture_id = ?", pastureId). Find(&neckRingEstrusList).Error; err != nil { zaplog.Error("UpdateEstrusIsPeak", zap.Any("Find", err)) } for _, estrus := range neckRingEstrusList { activeTime, _ := util.TimeParseLocal(model.LayoutTime, estrus.ActiveTime) if activeTime.Before(xToday) || activeTime.After(xToday.AddDate(0, 0, 1)) { continue } var exists bool if err := e.DB.Model(new(model.NeckRingEstrus)). Where("cow_id = ?", estrus.CowId). Where("first_time != ?", ""). Where("active_time BETWEEN ? AND ?", xToday, xToday.AddDate(0, 0, 1)). Where("active_time BETWEEN ? AND ?", estrus.FirstTime, activeTime.Add(-2*time.Hour)). Select("1"). Limit(1). Scan(&exists).Error; err != nil { zaplog.Error("UpdateEstrusIsPeak", zap.Any("exists", err)) continue } if exists { if err := e.DB.Model(estrus). Update("is_peak", pasturePb.IsShow_No).Error; err != nil { zaplog.Error("UpdateEstrusIsPeak", zap.Any("v", estrus), zap.Any("err", err)) } } } } // FindCowEstrusFirstTime1 查找牛只昨天是否有发情数据 func (e *Entry) FindCowEstrusFirstTime1(pastureId, cowId int64, xToday time.Time) *EstrusStartData { firstTimeEventEstrus := &EstrusStartData{} if err := e.DB.Model(new(model.NeckRingEstrus)). Select("cow_id,MIN(STR_TO_DATE(first_time, '%Y-%m-%d %H:%i:%s')) as first_time"). Where("active_time BETWEEN ? AND ?", fmt.Sprintf("%s 00:00:00", xToday.AddDate(0, 0, -1).Format(model.LayoutDate2)), fmt.Sprintf("%s 23:59:59", xToday.Format(model.LayoutDate2)), ).Where("pasture_id = ?", pastureId). Where("cow_id = ?", cowId). First(&firstTimeEventEstrus).Error; err != nil { return nil } return firstTimeEventEstrus } // calculateCFT 计算cft值 func calculateCFT(habit *model.NeckActiveHabit) (cft float32) { if habit.ChangeAdjust >= 10 { cft = (float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3) * float32(habit.FilterCorrect) / 100 } else { cft = float32(habit.ChangeFilter) * float32(habit.FilterCorrect) / 100 } switch { case habit.RuminaFilter > MaxRuminaAdJust: cft -= float32(5) case habit.RuminaFilter > 0: cft -= float32(habit.RuminaFilter) * 0.25 case habit.RuminaFilter < -MaxRuminaAdJust: cft -= -MaxRuminaAdJust * RumtoHeat default: cft -= float32(habit.RuminaFilter) * RumtoHeat } return cft } // calculateActiveLevel 计算活动量等级 func calculateActiveLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind { if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle { return pasturePb.EstrusLevel_Low } else if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh { return pasturePb.EstrusLevel_Middle } else { return pasturePb.EstrusLevel_High } } // getResult 根据b3数据计算结果 func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind { result := pasturePb.CheckResult_Pending if b3.CheckResult == pasturePb.CheckResult_Correct { result = pasturePb.CheckResult_Correct } if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust { result = pasturePb.CheckResult_Fail } return result }