neck_ring_estrus.go 8.8 KB

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