neck_ring_estrus.go 9.5 KB

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