neck_ring_estrus.go 8.8 KB

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