neck_ring_estrus.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  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.NeckRingEstrus)).
  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. neckRingEstrusList := make([]*model.NeckRingEstrus, 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 > 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. checkResult := getResult(before3Data, maxCft, cowEstrus)
  125. isPeak := pasturePb.IsShow_Ok
  126. zaplog.Info("CowEstrusWarning",
  127. zap.Any("level", level),
  128. zap.Any("checkResult", checkResult),
  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. newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, pasturePb.ExposeEstrusType_Neck_Ring, level, checkResult, isShow)
  141. newNeckRingEstrus.LastEstrusDate = lastEstrusDate
  142. newNeckRingEstrus.ActiveDate = activeDate
  143. newNeckRingEstrus.DayHigh = dayHigh
  144. newNeckRingEstrus.MaxHigh = maxHigh
  145. newNeckRingEstrus.IsPeak = isPeak
  146. neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
  147. }
  148. }
  149. if len(neckRingEstrusList) > 0 {
  150. if err = e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
  151. zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
  152. }
  153. }
  154. if err = e.UpdateEstrusStartDate(pastureId, nowTime); err != nil {
  155. zaplog.Error("UpdateEstrusStartDate", zap.Any("err", err))
  156. }
  157. return err
  158. }
  159. // UpdateEstrusStartDate 更新发情开始时间数据
  160. func (e *Entry) UpdateEstrusStartDate(pastureId int64, xToday time.Time) (err error) {
  161. beforeEventEstrus := make([]*EstrusStartData, 0)
  162. if err = e.DB.Model(new(model.NeckRingEstrus)).
  163. Select("cow_id,MIN(estrus_start_date) as estrus_start_date").
  164. Where("active_date BETWEEN ? AND ?", xToday.Add(-24*time.Hour).Format(model.LayoutTime), xToday.Format(model.LayoutTime)).
  165. Where("estrus_start_date != ?", "").
  166. Where("expose_estrus_type = ?", pasturePb.ExposeEstrusType_Neck_Ring).
  167. Where("pasture_id = ?", pastureId).
  168. Group("cow_id").Find(&beforeEventEstrus).Error; err != nil {
  169. return xerr.WithStack(err)
  170. }
  171. if len(beforeEventEstrus) > 0 {
  172. for _, v := range beforeEventEstrus {
  173. if err = e.DB.Model(new(model.NeckRingEstrus)).
  174. Where("cow_id = ?", v.CowId).
  175. Where("active_date >= ? AND <= ?", xToday.Add(-1*time.Hour).Format(model.LayoutTime), xToday.Add(24*time.Hour).Format(model.LayoutTime)).
  176. Update("estrus_start_date", v.EstrusStartDate).Error; err != nil {
  177. zaplog.Error("UpdateEstrusStartDate", zap.Any("err", err))
  178. }
  179. }
  180. }
  181. return nil
  182. }
  183. // UpdateIsPeak 更新IsPeak是否是高峰字段 1 是 0 否
  184. func (e *Entry) UpdateIsPeak(pastureId int64, xToday time.Time) (err error) {
  185. return nil
  186. }
  187. // calculateCFT 计算cft值
  188. func calculateCFT(habit *model.NeckActiveHabit) (cft float32) {
  189. cft = float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3
  190. if habit.ChangeAdjust < 10 {
  191. cft = float32(habit.ChangeFilter)
  192. }
  193. cft = cft * float32(habit.FilterCorrect) / 100
  194. ruminaAdjust := float32(0)
  195. switch {
  196. case habit.RuminaFilter > MaxRuminaAdJust:
  197. ruminaAdjust = float32(5)
  198. case habit.RuminaFilter > 0:
  199. ruminaAdjust = float32(habit.RuminaFilter) * 0.25
  200. case habit.RuminaFilter < -MaxRuminaAdJust:
  201. ruminaAdjust = -MaxRuminaAdJust * RumtoHeat
  202. default:
  203. ruminaAdjust = float32(habit.RuminaFilter) * RumtoHeat
  204. }
  205. cft -= ruminaAdjust
  206. return cft
  207. }
  208. // calculateLevel 计算发情等级
  209. func calculateLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
  210. level := pasturePb.EstrusLevel_High
  211. if int32(cft)+cowEstrus.HadJust < int32(xToday.ActiveMiddle) {
  212. level = pasturePb.EstrusLevel_Low
  213. }
  214. if int32(cft)+cowEstrus.HadJust >= int32(xToday.ActiveHigh) {
  215. level = pasturePb.EstrusLevel_Middle
  216. }
  217. return level
  218. }
  219. // getResult 根据b3数据计算结果 0 1 2 3 -1 -2
  220. func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
  221. result := pasturePb.CheckResult_Invalid
  222. if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
  223. result = pasturePb.CheckResult_Fail
  224. }
  225. if b3.CheckResult == pasturePb.CheckResult_Overdue {
  226. result = pasturePb.CheckResult_Correct
  227. }
  228. return result
  229. }
  230. // sortHabitsByChangeFilter 根据change_filter排序
  231. func sortHabitsByChangeFilter(habits []*model.NeckActiveHabit) []*model.NeckActiveHabit {
  232. sorted := make([]*model.NeckActiveHabit, len(habits))
  233. copy(sorted, habits)
  234. for i := range sorted {
  235. for j := i + 1; j < len(sorted); j++ {
  236. if sorted[i].ChangeFilter < sorted[j].ChangeFilter {
  237. sorted[i], sorted[j] = sorted[j], sorted[i]
  238. }
  239. }
  240. }
  241. return sorted
  242. }