neck_ring_estrus.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. package crontab
  2. import (
  3. "fmt"
  4. "kpt-pasture/model"
  5. "kpt-pasture/util"
  6. "time"
  7. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  8. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  9. "go.uber.org/zap"
  10. "gitee.com/xuyiping_admin/pkg/xerr"
  11. )
  12. const (
  13. MaxRuminaAdJust = 20
  14. XAdjust21 = 15
  15. XAdjust42 = 10
  16. RumtoHeat = 0.5
  17. MinCalvingAge = 20
  18. MinLact = 0
  19. NormalChangJust = 10
  20. B48 = 48
  21. )
  22. func (e *Entry) UpdateCowEstrus() (err error) {
  23. pastureList := e.FindPastureList()
  24. if pastureList == nil || len(pastureList) == 0 {
  25. return nil
  26. }
  27. for _, pasture := range pastureList {
  28. if err = e.EntryCowEstrus(pasture.Id); err != nil {
  29. zaplog.Error("EntryCrontab", zap.Any("PastureUpdateCowEstrus", err), zap.Any("pasture", pasture))
  30. }
  31. zaplog.Info("PastureUpdateCowEstrus-success", zap.Any("pasture", pasture.Id))
  32. }
  33. return nil
  34. }
  35. func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
  36. xToday := &XToday{}
  37. systemConfigureList, err := e.GetSystemNeckRingConfigure(pastureId)
  38. if err != nil {
  39. return xerr.WithStack(err)
  40. }
  41. if systemConfigureList == nil || len(systemConfigureList) == 0 {
  42. return nil
  43. }
  44. for _, v := range systemConfigureList {
  45. switch v.Name {
  46. case model.ActiveLow:
  47. xToday.ActiveLow = int32(v.Value)
  48. case model.ActiveMiddle:
  49. xToday.ActiveMiddle = int32(v.Value)
  50. case model.ActiveHigh:
  51. xToday.ActiveHigh = int32(v.Value)
  52. }
  53. }
  54. nowTime := time.Now()
  55. e.CowEstrusWarning(pastureId, xToday, nowTime)
  56. e.UpdateNewNeckRingEstrus(pastureId, nowTime)
  57. return nil
  58. }
  59. // CowEstrusWarning 发情预警
  60. func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.Time) {
  61. neckActiveHabitList := make([]*model.NeckActiveHabit, 0) // todo 需要考虑到数据量太大的情况
  62. if err := e.DB.Model(new(model.NeckActiveHabit)).
  63. Where("heat_date = ?", nowTime.Format(model.LayoutDate2)).
  64. Where("pasture_id = ?", pastureId).
  65. Where("filter_high > 0 AND change_filter > ?", model.DefaultChangeFilter).
  66. Where("cow_id > ?", 0).
  67. Where(e.DB.Where("calving_age >= ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛
  68. Order("cow_id").
  69. Find(&neckActiveHabitList).Error; err != nil {
  70. zaplog.Error("CowEstrusWarning", zap.Any("Find", err))
  71. return
  72. }
  73. zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitList", neckActiveHabitList))
  74. neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit)
  75. for _, habit := range neckActiveHabitList {
  76. cft := calculateCFT(habit)
  77. if cft < float32(xToday.ActiveLow-XAdjust21) {
  78. continue
  79. }
  80. zaplog.Info("CowEstrusWarning", zap.Any("cft", cft), zap.Any("habit", habit))
  81. if neckActiveHabitMap[habit.CowId] == nil {
  82. neckActiveHabitMap[habit.CowId] = make([]*model.NeckActiveHabit, 0)
  83. }
  84. neckActiveHabitMap[habit.CowId] = append(neckActiveHabitMap[habit.CowId], habit)
  85. }
  86. zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitMap", neckActiveHabitMap))
  87. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  88. for cowId, cowHabitList := range neckActiveHabitMap {
  89. // 最近3天最大发情记录,小于该变化趋势的不再插入
  90. before3Data := e.GetBeforeThreeDaysCowEstrus(cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  91. // 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42
  92. cowEstrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  93. activeDateTime, _ := util.TimeParseLocal(model.LayoutTime, cowEstrus.ActiveDate)
  94. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() {
  95. cowEstrus.HadJust = XAdjust21
  96. }
  97. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -50).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -36).Unix() {
  98. cowEstrus.HadJust = XAdjust42
  99. }
  100. maxCft := float32(0)
  101. maxHigh := int32(0)
  102. lastActiveDate := time.Time{}
  103. for _, habit := range cowHabitList {
  104. cft := calculateCFT(habit)
  105. if cft > maxCft {
  106. maxCft = cft
  107. }
  108. if habit.FilterHigh > maxHigh {
  109. maxHigh = habit.FilterHigh
  110. }
  111. // 获取最新的 CreateTime
  112. activeTimeParse, _ := util.TimeParseLocal(model.LayoutTime, habit.ActiveTime)
  113. if activeTimeParse.After(lastActiveDate) {
  114. lastActiveDate = activeTimeParse
  115. }
  116. }
  117. b48 := float64(0)
  118. t3, _ := util.TimeParseLocal(model.LayoutTime, before3Data.ActiveTime)
  119. b48 = t3.Sub(lastActiveDate).Hours()
  120. if (int32(maxCft) > before3Data.DayHigh || before3Data.CowId == 0 || b48 > B48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow {
  121. level := calculateActiveLevel(maxCft, cowEstrus, xToday)
  122. cowInfo := e.FindCowInfoByCowId(cowId)
  123. if cowInfo == nil {
  124. zaplog.Error("CowEstrusWarning", zap.Any("FindCowInfoByCowId", cowId))
  125. continue
  126. }
  127. isShow := pasturePb.IsShow_Ok
  128. if cowInfo != nil && cowInfo.IsPregnant == pasturePb.IsShow_Ok && level == pasturePb.EstrusLevel_Low {
  129. isShow = pasturePb.IsShow_No
  130. }
  131. dayHigh := int32(maxCft) + cowEstrus.HadJust
  132. lastEstrusDate := cowEstrus.ActiveDate
  133. checkResult := getResult(before3Data, maxCft, cowEstrus)
  134. isPeak := pasturePb.IsShow_Ok
  135. zaplog.Info("CowEstrusWarning",
  136. zap.Any("level", level),
  137. zap.Any("b48", b48),
  138. zap.Any("checkResult", checkResult),
  139. zap.Any("isShow", isShow),
  140. zap.Any("isPeak", isPeak),
  141. zap.Any("lastEstrusDate", lastEstrusDate),
  142. zap.Any("activeDate", lastActiveDate),
  143. zap.Any("dayHigh", dayHigh),
  144. zap.Any("cft", maxCft),
  145. zap.Any("before3Data", before3Data),
  146. zap.Any("cowEstrus", cowEstrus),
  147. zap.Any("cowInfo", cowInfo),
  148. zap.Any("cowHabitList", cowHabitList),
  149. )
  150. newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, level, checkResult, isShow)
  151. newNeckRingEstrus.LastTime = lastEstrusDate
  152. newNeckRingEstrus.ActiveTime = lastActiveDate.Format(model.LayoutTime)
  153. newNeckRingEstrus.DayHigh = dayHigh
  154. newNeckRingEstrus.MaxHigh = maxHigh
  155. newNeckRingEstrus.IsPeak = isPeak
  156. neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
  157. }
  158. }
  159. zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList))
  160. if len(neckRingEstrusList) > 0 {
  161. if err := e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
  162. zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
  163. }
  164. }
  165. }
  166. func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, nowTime time.Time) {
  167. // 更新牛只首次发情时间
  168. e.UpdateEstrusFirstTime1(pastureId, nowTime)
  169. e.UpdateEstrusIsPeak(pastureId, nowTime)
  170. e.UpdateEstrusFirstTime2(pastureId, nowTime)
  171. e.UpdateEstrusFirstTime3(pastureId, nowTime)
  172. }
  173. // UpdateEstrusFirstTime1 更新牛只首次发情时间
  174. func (e *Entry) UpdateEstrusFirstTime1(pastureId int64, xToday time.Time) {
  175. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  176. for _, v := range neckRingEstrusList {
  177. cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v.CowId, xToday)
  178. if cowEstrusStartData != nil && cowEstrusStartData.FirstTime != "" {
  179. if err := e.DB.Model(new(model.NeckRingEstrus)).
  180. Where("id = ?", v.Id).
  181. Update("first_time", cowEstrusStartData.FirstTime).Error; err != nil {
  182. zaplog.Error("UpdateEstrusFirstTime1",
  183. zap.Any("v", v),
  184. zap.Any("err", err),
  185. zap.Any("cowEstrusStartData", cowEstrusStartData),
  186. )
  187. }
  188. }
  189. }
  190. }
  191. func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday time.Time) {
  192. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  193. for _, v := range neckRingEstrusList {
  194. zaplog.Info("UpdateEstrusFirstTime2", zap.Any("v", v))
  195. }
  196. }
  197. func (e *Entry) UpdateEstrusFirstTime3(pastureId int64, xToday time.Time) {
  198. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  199. for _, v := range neckRingEstrusList {
  200. activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
  201. if activeTime.After(xToday.AddDate(0, 0, -2)) {
  202. if err := e.DB.Model(new(model.NeckRingEstrus)).
  203. Where("id = ?", v.Id).
  204. Update("first_time", v.ActiveTime).Error; err != nil {
  205. zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err))
  206. }
  207. }
  208. }
  209. }
  210. func (e *Entry) UpdateEstrusIsPeak(pastureId int64, xToday time.Time) {
  211. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  212. if err := e.DB.Model(new(model.NeckRingEstrus)).
  213. Where("first_time != ?", "").
  214. Where("is_peak = ?", pasturePb.IsShow_Ok).
  215. Where("is_show = ?", pasturePb.IsShow_Ok).
  216. Where("pasture_id = ?", pastureId).
  217. Find(&neckRingEstrusList).Error; err != nil {
  218. zaplog.Error("UpdateEstrusIsPeak", zap.Any("Find", err))
  219. }
  220. for _, estrus := range neckRingEstrusList {
  221. activeTime, _ := util.TimeParseLocal(model.LayoutTime, estrus.ActiveTime)
  222. if activeTime.Before(xToday) || activeTime.After(xToday.AddDate(0, 0, 1)) {
  223. continue
  224. }
  225. var exists bool
  226. if err := e.DB.Model(new(model.NeckRingEstrus)).
  227. Where("cow_id = ?", estrus.CowId).
  228. Where("first_time != ?", "").
  229. Where("active_time BETWEEN ? AND ?", xToday, xToday.AddDate(0, 0, 1)).
  230. Where("active_time BETWEEN ? AND ?", estrus.FirstTime, activeTime.Add(-2*time.Hour)).
  231. Select("1").
  232. Limit(1).
  233. Scan(&exists).Error; err != nil {
  234. zaplog.Error("UpdateEstrusIsPeak", zap.Any("exists", err))
  235. continue
  236. }
  237. if exists {
  238. if err := e.DB.Model(estrus).
  239. Update("is_peak", pasturePb.IsShow_No).Error; err != nil {
  240. zaplog.Error("UpdateEstrusIsPeak", zap.Any("v", estrus), zap.Any("err", err))
  241. }
  242. }
  243. }
  244. }
  245. // FindCowEstrusFirstTime1 查找牛只昨天是否有发情数据
  246. func (e *Entry) FindCowEstrusFirstTime1(pastureId, cowId int64, xToday time.Time) *EstrusStartData {
  247. firstTimeEventEstrus := &EstrusStartData{}
  248. if err := e.DB.Model(new(model.NeckRingEstrus)).
  249. Select("cow_id,MIN(STR_TO_DATE(first_time, '%Y-%m-%d %H:%i:%s')) as first_time").
  250. Where("active_time BETWEEN ? AND ?",
  251. fmt.Sprintf("%s 00:00:00", xToday.AddDate(0, 0, -1).Format(model.LayoutDate2)),
  252. fmt.Sprintf("%s 23:59:59", xToday.Format(model.LayoutDate2)),
  253. ).Where("pasture_id = ?", pastureId).
  254. Where("cow_id = ?", cowId).
  255. First(&firstTimeEventEstrus).Error; err != nil {
  256. return nil
  257. }
  258. return firstTimeEventEstrus
  259. }
  260. // calculateCFT 计算cft值
  261. func calculateCFT(habit *model.NeckActiveHabit) (cft float32) {
  262. if habit.ChangeAdjust >= 10 {
  263. cft = (float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3) * float32(habit.FilterCorrect) / 100
  264. } else {
  265. cft = float32(habit.ChangeFilter) * float32(habit.FilterCorrect) / 100
  266. }
  267. switch {
  268. case habit.RuminaFilter > MaxRuminaAdJust:
  269. cft -= float32(5)
  270. case habit.RuminaFilter > 0:
  271. cft -= float32(habit.RuminaFilter) * 0.25
  272. case habit.RuminaFilter < -MaxRuminaAdJust:
  273. cft -= -MaxRuminaAdJust * RumtoHeat
  274. default:
  275. cft -= float32(habit.RuminaFilter) * RumtoHeat
  276. }
  277. return cft
  278. }
  279. // calculateActiveLevel 计算活动量等级
  280. func calculateActiveLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
  281. if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle {
  282. return pasturePb.EstrusLevel_Low
  283. } else if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh {
  284. return pasturePb.EstrusLevel_Middle
  285. } else {
  286. return pasturePb.EstrusLevel_High
  287. }
  288. }
  289. // getResult 根据b3数据计算结果
  290. func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
  291. result := pasturePb.CheckResult_Pending
  292. if b3.CheckResult == pasturePb.CheckResult_Correct {
  293. result = pasturePb.CheckResult_Correct
  294. }
  295. if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
  296. result = pasturePb.CheckResult_Fail
  297. }
  298. return result
  299. }