neck_ring_estrus.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package crontab
  2. import (
  3. "kpt-pasture/model"
  4. "time"
  5. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  6. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  7. "go.uber.org/zap"
  8. "gitee.com/xuyiping_admin/pkg/xerr"
  9. )
  10. const (
  11. MaxRuminaAdJust = 20
  12. XAdjust21 = 15
  13. XAdjust42 = 10
  14. RumtoHeat = 0.5
  15. MinCalvingAge = 20
  16. MinLact = 0
  17. NormalChangJust = 10
  18. )
  19. func (e *Entry) UpdateCowEstrus() (err error) {
  20. pastureList := e.FindPastureList()
  21. if pastureList == nil || len(pastureList) == 0 {
  22. return nil
  23. }
  24. for _, pasture := range pastureList {
  25. if err = e.EntryCowEstrus(pasture.Id); err != nil {
  26. zaplog.Error("EntryCrontab", zap.Any("PastureUpdateCowEstrus", err), zap.Any("pasture", pasture))
  27. }
  28. zaplog.Info("PastureUpdateCowEstrus-success", zap.Any("pasture", pasture.Id))
  29. }
  30. return nil
  31. }
  32. func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
  33. xToday := &XToday{}
  34. systemConfigureList, err := e.GetSystemConfigure(pastureId)
  35. for _, v := range systemConfigureList {
  36. switch v.Name {
  37. case model.ActiveLow:
  38. xToday.ActiveLow = int32(v.Value)
  39. case model.ActiveMiddle:
  40. xToday.ActiveMiddle = int32(v.Value)
  41. case model.ActiveHigh:
  42. xToday.ActiveHigh = int32(v.Value)
  43. }
  44. }
  45. if err = e.CowEstrusWarning(pastureId, xToday); err != nil {
  46. zaplog.Error("EntryCowEstrus", zap.Any("CowEstrusWarning", err), zap.Any("xToday", xToday))
  47. }
  48. // 将历史发情预警数据更新为已过期
  49. if err = e.DB.Model(new(model.EventEstrus)).
  50. Where("estrus_start_date <= ?", time.Now().AddDate(0, 0, -4).Format(model.LayoutTime)).
  51. Update("is_show", pasturePb.IsShow_No).Error; err != nil {
  52. zaplog.Error("EntryCowEstrus", zap.Any("UpdateEventEstrus", err))
  53. }
  54. return nil
  55. }
  56. // CowEstrusWarning 发情预警
  57. func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
  58. nowTime := time.Now()
  59. neckActiveHabitList := make([]*model.NeckActiveHabit, 0) // todo 需要考虑到数据量太大的情况
  60. if err = e.DB.Model(new(model.NeckActiveHabit)).
  61. Where("heat_date = ?", nowTime.Format(model.LayoutDate2)).
  62. Where("pasture_id = ?", pastureId).
  63. Where("filter_high > 0 AND change_filter > ?", model.DefaultChangeFilter).
  64. Where("cow_id > ?", 0).
  65. Where(e.DB.Where("calving_age > ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛
  66. Find(&neckActiveHabitList).Error; err != nil {
  67. return xerr.WithStack(err)
  68. }
  69. neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit)
  70. for _, habit := range neckActiveHabitList {
  71. cft := calculateCFT(habit)
  72. if cft < float32(xToday.ActiveLow-XAdjust21) {
  73. continue
  74. }
  75. if _, ok := neckActiveHabitMap[habit.CowId]; !ok {
  76. neckActiveHabitMap[habit.CowId] = make([]*model.NeckActiveHabit, 0)
  77. }
  78. neckActiveHabitMap[habit.CowId] = append(neckActiveHabitMap[habit.CowId], habit)
  79. }
  80. eventEstrusList := make([]*model.EventEstrus, 0)
  81. for cowId, cowHabitList := range neckActiveHabitMap {
  82. // 最近3天最大发情记录,小于该变化趋势的不再插入
  83. before3Data := e.GetBeforeThreeDaysCowEstrus(cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  84. // 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42
  85. cowEstrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  86. activeDateTime, _ := time.Parse(model.LayoutTime, cowEstrus.ActiveDate)
  87. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() {
  88. cowEstrus.HadJust = XAdjust21
  89. }
  90. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -50).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -36).Unix() {
  91. cowEstrus.HadJust = XAdjust42
  92. }
  93. maxCft := float32(0)
  94. maxHigh := int32(0)
  95. for _, habit := range cowHabitList {
  96. cft := calculateCFT(habit)
  97. if cft > maxCft {
  98. maxCft = cft
  99. }
  100. if habit.FilterHigh > maxHigh {
  101. maxHigh = habit.FilterHigh
  102. }
  103. }
  104. activeDate := ""
  105. if len(cowHabitList) > 0 {
  106. sortHabits := sortHabitsByChangeFilter(cowHabitList)
  107. activeDate = sortHabits[0].ActiveTime
  108. }
  109. b48 := float64(0)
  110. t1, _ := time.Parse(model.LayoutTime, activeDate)
  111. t3, err := time.Parse(model.LayoutTime, before3Data.ActiveDate)
  112. if err == nil {
  113. b48 = t3.Sub(t1).Hours()
  114. }
  115. if (int32(maxCft) > before3Data.DayHigh || b48 > 48) && int32(maxCft)+cowEstrus.HadJust > int32(xToday.ActiveLow) {
  116. level := calculateLevel(maxCft, cowEstrus, xToday)
  117. cowInfo := e.FindCowInfoByNeckRingNumber(cowHabitList[0].NeckRingNumber)
  118. isShow := pasturePb.IsShow_Ok
  119. if cowInfo.IsPregnant == pasturePb.IsShow_Ok && level == pasturePb.EstrusLevel_Low {
  120. isShow = pasturePb.IsShow_No
  121. }
  122. dayHigh := int32(maxCft) + cowEstrus.HadJust
  123. lastEstrusDate := cowEstrus.ActiveDate
  124. result := getResult(before3Data, maxCft, cowEstrus)
  125. isPeak := pasturePb.IsShow_Ok
  126. zaplog.Info("CowEstrusWarning",
  127. zap.Any("level", level),
  128. zap.Any("result", result),
  129. zap.Any("isShow", isShow),
  130. zap.Any("isPeak", isPeak),
  131. zap.Any("lastEstrusDate", lastEstrusDate),
  132. zap.Any("activeDate", activeDate),
  133. zap.Any("dayHigh", dayHigh),
  134. zap.Any("cft", maxCft),
  135. zap.Any("before3Data", before3Data),
  136. zap.Any("cowEstrus", cowEstrus),
  137. zap.Any("cowInfo", cowInfo),
  138. zap.Any("cowHabitList", cowHabitList),
  139. )
  140. newEstrus := model.NewEventEstrus(
  141. pastureId, pasturePb.ExposeEstrusType_Neck_Ring, level, result, isShow,
  142. isPeak, lastEstrusDate, activeDate, dayHigh, maxHigh, cowInfo,
  143. )
  144. eventEstrusList = append(eventEstrusList, newEstrus)
  145. }
  146. }
  147. if len(eventEstrusList) > 0 {
  148. if err = e.DB.Model(new(model.EventEstrus)).Create(eventEstrusList).Error; err != nil {
  149. zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", eventEstrusList), zap.Any("err", err))
  150. }
  151. }
  152. if err = e.UpdateEstrusStartDate(pastureId, nowTime); err != nil {
  153. zaplog.Error("UpdateEstrusStartDate", zap.Any("err", err))
  154. }
  155. return err
  156. }
  157. // UpdateEstrusStartDate 更新发情开始时间数据
  158. func (e *Entry) UpdateEstrusStartDate(pastureId int64, xToday time.Time) (err error) {
  159. beforeEventEstrus := make([]*EstrusStartData, 0)
  160. if err = e.DB.Model(new(model.EventEstrus)).
  161. Select("cow_id,MIN(estrus_start_date) as estrus_start_date").
  162. Where("active_date BETWEEN ? AND ?", xToday.Add(-24*time.Hour).Format(model.LayoutTime), xToday.Format(model.LayoutTime)).
  163. Where("estrus_start_date != ?", "").
  164. Where("expose_estrus_type = ?", pasturePb.ExposeEstrusType_Neck_Ring).
  165. Where("pasture_id = ?", pastureId).
  166. Group("cow_id").Find(&beforeEventEstrus).Error; err != nil {
  167. return xerr.WithStack(err)
  168. }
  169. if len(beforeEventEstrus) > 0 {
  170. for _, v := range beforeEventEstrus {
  171. if err = e.DB.Model(new(model.EventEstrus)).
  172. Where("cow_id = ?", v.CowId).
  173. Where("active_date >= ? AND <= ?", xToday.Add(-1*time.Hour).Format(model.LayoutTime), xToday.Add(24*time.Hour).Format(model.LayoutTime)).
  174. Update("estrus_start_date", v.EstrusStartDate).Error; err != nil {
  175. zaplog.Error("UpdateEstrusStartDate", zap.Any("err", err))
  176. }
  177. }
  178. }
  179. return nil
  180. }
  181. // UpdateIsPeak 更新IsPeak是否是高峰字段 1 是 0 否
  182. func (e *Entry) UpdateIsPeak(pastureId int64, xToday time.Time) (err error) {
  183. return nil
  184. }
  185. // calculateCFT 计算cft值
  186. func calculateCFT(habit *model.NeckActiveHabit) (cft float32) {
  187. cft = float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3
  188. if habit.ChangeAdjust < 10 {
  189. cft = float32(habit.ChangeFilter)
  190. }
  191. cft = cft * float32(habit.FilterCorrect) / 100
  192. ruminaAdjust := float32(0)
  193. switch {
  194. case habit.RuminaFilter > MaxRuminaAdJust:
  195. ruminaAdjust = float32(5)
  196. case habit.RuminaFilter > 0:
  197. ruminaAdjust = float32(habit.RuminaFilter) * 0.25
  198. case habit.RuminaFilter < -MaxRuminaAdJust:
  199. ruminaAdjust = -MaxRuminaAdJust * RumtoHeat
  200. default:
  201. ruminaAdjust = float32(habit.RuminaFilter) * RumtoHeat
  202. }
  203. cft -= ruminaAdjust
  204. return cft
  205. }
  206. // calculateLevel 计算发情等级
  207. func calculateLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
  208. level := pasturePb.EstrusLevel_High
  209. if int32(cft)+cowEstrus.HadJust < int32(xToday.ActiveMiddle) {
  210. level = pasturePb.EstrusLevel_Low
  211. }
  212. if int32(cft)+cowEstrus.HadJust >= int32(xToday.ActiveHigh) {
  213. level = pasturePb.EstrusLevel_Middle
  214. }
  215. return level
  216. }
  217. // getResult 根据b3数据计算结果 0 1 2 3 -1 -2
  218. func getResult(b3 *model.EventEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
  219. result := pasturePb.CheckResult_Invalid
  220. if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
  221. result = pasturePb.CheckResult_Fail
  222. }
  223. if b3.CheckResult == pasturePb.CheckResult_Overdue {
  224. result = pasturePb.CheckResult_Correct
  225. }
  226. return result
  227. }
  228. // sortHabitsByChangeFilter 根据change_filter排序
  229. func sortHabitsByChangeFilter(habits []*model.NeckActiveHabit) []*model.NeckActiveHabit {
  230. sorted := make([]*model.NeckActiveHabit, len(habits))
  231. copy(sorted, habits)
  232. for i := range sorted {
  233. for j := i + 1; j < len(sorted); j++ {
  234. if sorted[i].ChangeFilter < sorted[j].ChangeFilter {
  235. sorted[i], sorted[j] = sorted[j], sorted[i]
  236. }
  237. }
  238. }
  239. return sorted
  240. }