neck_ring_estrus.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. package crontab
  2. import (
  3. "fmt"
  4. "kpt-pasture/model"
  5. "time"
  6. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  7. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  8. "go.uber.org/zap"
  9. "gitee.com/xuyiping_admin/pkg/xerr"
  10. )
  11. const (
  12. MaxRuminaAdJust = 20
  13. XAdjust21 = 15
  14. XAdjust42 = 10
  15. RumtoHeat = 0.5
  16. MinCalvingAge = 20
  17. MinLact = 0
  18. NormalChangJust = 10
  19. B48 = 48
  20. )
  21. func (e *Entry) UpdateCowEstrus() (err error) {
  22. pastureList := e.FindPastureList()
  23. if pastureList == nil || len(pastureList) == 0 {
  24. return nil
  25. }
  26. for _, pasture := range pastureList {
  27. if err = e.EntryCowEstrus(pasture.Id); err != nil {
  28. zaplog.Error("EntryCrontab", zap.Any("PastureUpdateCowEstrus", err), zap.Any("pasture", pasture))
  29. }
  30. zaplog.Info("PastureUpdateCowEstrus-success", zap.Any("pasture", pasture.Id))
  31. }
  32. return nil
  33. }
  34. func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
  35. xToday := &XToday{}
  36. systemConfigureList, err := e.GetSystemNeckRingConfigure(pastureId)
  37. for _, v := range systemConfigureList {
  38. switch v.Name {
  39. case model.ActiveLow:
  40. xToday.ActiveLow = int32(v.Value)
  41. case model.ActiveMiddle:
  42. xToday.ActiveMiddle = int32(v.Value)
  43. case model.ActiveHigh:
  44. xToday.ActiveHigh = int32(v.Value)
  45. }
  46. }
  47. if err = e.CowEstrusWarning(pastureId, xToday); err != nil {
  48. zaplog.Error("EntryCowEstrus", zap.Any("CowEstrusWarning", err), zap.Any("xToday", xToday))
  49. }
  50. // 将3天前历史发情预警数据更新为已过期
  51. if err = e.DB.Model(new(model.NeckRingEstrus)).
  52. Where("first_time != ?", "").
  53. Where("first_time <= ?", time.Now().AddDate(0, 0, -3).Format(model.LayoutTime)).
  54. Update("check_result", pasturePb.CheckResult_Overdue).Error; err != nil {
  55. zaplog.Error("EntryCowEstrus", zap.Any("UpdateEventEstrus", err))
  56. }
  57. return nil
  58. }
  59. // CowEstrusWarning 发情预警
  60. func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
  61. nowTime := time.Now()
  62. neckActiveHabitList := make([]*model.NeckActiveHabit, 0) // todo 需要考虑到数据量太大的情况
  63. if err = e.DB.Model(new(model.NeckActiveHabit)).
  64. Where("heat_date = ?", nowTime.Format(model.LayoutDate2)).
  65. Where("pasture_id = ?", pastureId).
  66. Where("filter_high > 0 AND change_filter > ?", model.DefaultChangeFilter).
  67. Where("cow_id > ?", 0).
  68. Where(e.DB.Where("calving_age >= ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛
  69. Order("cow_id").
  70. Find(&neckActiveHabitList).Error; err != nil {
  71. return xerr.WithStack(err)
  72. }
  73. zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitList", neckActiveHabitList))
  74. neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit)
  75. for _, habit := range neckActiveHabitList {
  76. cft := calculateCFT(habit)
  77. if cft < float32(xToday.ActiveLow-XAdjust21) {
  78. continue
  79. }
  80. if neckActiveHabitMap[habit.CowId] == nil {
  81. neckActiveHabitMap[habit.CowId] = make([]*model.NeckActiveHabit, 0)
  82. }
  83. neckActiveHabitMap[habit.CowId] = append(neckActiveHabitMap[habit.CowId], habit)
  84. }
  85. zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitMap", neckActiveHabitMap))
  86. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  87. for cowId, cowHabitList := range neckActiveHabitMap {
  88. // 最近3天最大发情记录,小于该变化趋势的不再插入
  89. before3Data := e.GetBeforeThreeDaysCowEstrus(cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  90. // 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42
  91. cowEstrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  92. activeDateTime, _ := time.Parse(model.LayoutTime, cowEstrus.ActiveDate)
  93. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() {
  94. cowEstrus.HadJust = XAdjust21
  95. }
  96. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -50).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -36).Unix() {
  97. cowEstrus.HadJust = XAdjust42
  98. }
  99. maxCft := float32(0)
  100. maxHigh := int32(0)
  101. lastActiveDate := time.Time{}
  102. for _, habit := range cowHabitList {
  103. cft := calculateCFT(habit)
  104. if cft > maxCft {
  105. maxCft = cft
  106. }
  107. if habit.FilterHigh > maxHigh {
  108. maxHigh = habit.FilterHigh
  109. }
  110. // 获取最新的 CreateTime
  111. activeTimeParse, _ := time.Parse(model.LayoutTime, habit.ActiveTime)
  112. if activeTimeParse.After(lastActiveDate) {
  113. lastActiveDate = activeTimeParse
  114. }
  115. }
  116. b48 := float64(0)
  117. t3, _ := time.Parse(model.LayoutTime, before3Data.ActiveTime)
  118. b48 = t3.Sub(lastActiveDate).Hours()
  119. if (int32(maxCft) > before3Data.DayHigh || before3Data.CowId == 0 || b48 > B48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow {
  120. level := calculateActiveLevel(maxCft, cowEstrus, xToday)
  121. cowInfo := e.FindCowInfoByCowId(cowId)
  122. if cowInfo == nil {
  123. zaplog.Error("CowEstrusWarning", zap.Any("FindCowInfoByCowId", cowId))
  124. continue
  125. }
  126. isShow := pasturePb.IsShow_Ok
  127. if cowInfo != nil && cowInfo.IsPregnant == pasturePb.IsShow_Ok && level == pasturePb.EstrusLevel_Low {
  128. isShow = pasturePb.IsShow_No
  129. }
  130. dayHigh := int32(maxCft) + cowEstrus.HadJust
  131. lastEstrusDate := cowEstrus.ActiveDate
  132. checkResult := getResult(before3Data, maxCft, cowEstrus)
  133. isPeak := pasturePb.IsShow_Ok
  134. zaplog.Info("CowEstrusWarning",
  135. zap.Any("level", level),
  136. zap.Any("b48", b48),
  137. zap.Any("checkResult", checkResult),
  138. zap.Any("isShow", isShow),
  139. zap.Any("isPeak", isPeak),
  140. zap.Any("lastEstrusDate", lastEstrusDate),
  141. zap.Any("activeDate", lastActiveDate),
  142. zap.Any("dayHigh", dayHigh),
  143. zap.Any("cft", maxCft),
  144. zap.Any("before3Data", before3Data),
  145. zap.Any("cowEstrus", cowEstrus),
  146. zap.Any("cowInfo", cowInfo),
  147. zap.Any("cowHabitList", cowHabitList),
  148. )
  149. newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, level, checkResult, isShow)
  150. newNeckRingEstrus.LastTime = lastEstrusDate
  151. newNeckRingEstrus.ActiveTime = lastActiveDate.Format(model.LayoutTime)
  152. newNeckRingEstrus.DayHigh = dayHigh
  153. newNeckRingEstrus.MaxHigh = maxHigh
  154. newNeckRingEstrus.IsPeak = isPeak
  155. neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
  156. }
  157. }
  158. zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList))
  159. if len(neckRingEstrusList) > 0 {
  160. if err = e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
  161. zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
  162. }
  163. }
  164. // 更新牛只首次发情时间
  165. e.UpdateEstrusFirstTime1(pastureId, nowTime)
  166. e.UpdateEstrusIsPeak(pastureId, nowTime)
  167. e.UpdateEstrusFirstTime2(pastureId, nowTime)
  168. e.UpdateEstrusFirstTime3(pastureId, nowTime)
  169. return nil
  170. }
  171. // UpdateEstrusFirstTime1 更新牛只首次发情时间
  172. func (e *Entry) UpdateEstrusFirstTime1(pastureId int64, xToday time.Time) {
  173. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  174. for _, v := range neckRingEstrusList {
  175. cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v.CowId, xToday)
  176. if cowEstrusStartData != nil && cowEstrusStartData.FirstTime != "" {
  177. if err := e.DB.Model(new(model.NeckRingEstrus)).
  178. Where("id = ?", v.Id).
  179. Update("first_time", cowEstrusStartData.FirstTime).Error; err != nil {
  180. zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err), zap.Any("cowEstrusStartData", cowEstrusStartData))
  181. }
  182. }
  183. }
  184. }
  185. func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday time.Time) {
  186. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  187. for _, v := range neckRingEstrusList {
  188. zaplog.Info("UpdateEstrusFirstTime2", zap.Any("v", v))
  189. }
  190. }
  191. func (e *Entry) UpdateEstrusFirstTime3(pastureId int64, xToday time.Time) {
  192. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  193. for _, v := range neckRingEstrusList {
  194. activeTime, _ := time.Parse(model.LayoutTime, v.ActiveTime)
  195. if activeTime.After(xToday.AddDate(0, 0, -2)) {
  196. if err := e.DB.Model(new(model.NeckRingEstrus)).
  197. Where("id = ?", v.Id).
  198. Update("first_time", v.ActiveTime).Error; err != nil {
  199. zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err))
  200. }
  201. }
  202. }
  203. }
  204. func (e *Entry) UpdateEstrusIsPeak(pastureId int64, xToday time.Time) {
  205. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  206. if err := e.DB.Model(new(model.NeckRingEstrus)).
  207. Where("first_time != ?", "").
  208. Where("is_peak = ?", pasturePb.IsShow_Ok).
  209. Where("is_show = ?", pasturePb.IsShow_Ok).
  210. Where("pasture_id = ?", pastureId).
  211. Find(&neckRingEstrusList).Error; err != nil {
  212. zaplog.Error("UpdateEstrusIsPeak", zap.Any("Find", err))
  213. }
  214. for _, estrus := range neckRingEstrusList {
  215. activeTime, _ := time.Parse(model.LayoutTime, estrus.ActiveTime)
  216. if activeTime.Before(xToday) || activeTime.After(xToday.AddDate(0, 0, 1)) {
  217. continue
  218. }
  219. var exists bool
  220. if err := e.DB.Model(new(model.NeckRingEstrus)).
  221. Where("cow_id = ?", estrus.CowId).
  222. Where("first_time != ?", "").
  223. Where("active_time BETWEEN ? AND ?", xToday, xToday.AddDate(0, 0, 1)).
  224. Where("active_time BETWEEN ? AND ?", estrus.FirstTime, activeTime.Add(-2*time.Hour)).
  225. Select("1").
  226. Limit(1).
  227. Scan(&exists).Error; err != nil {
  228. zaplog.Error("UpdateEstrusIsPeak", zap.Any("exists", err))
  229. continue
  230. }
  231. if exists {
  232. if err := e.DB.Model(estrus).
  233. Update("is_peak", pasturePb.IsShow_No).Error; err != nil {
  234. zaplog.Error("UpdateEstrusIsPeak", zap.Any("v", estrus), zap.Any("err", err))
  235. }
  236. }
  237. }
  238. }
  239. // FindCowEstrusFirstTime1 查找牛只昨天是否有发情数据
  240. func (e *Entry) FindCowEstrusFirstTime1(pastureId, cowId int64, xToday time.Time) *EstrusStartData {
  241. firstTimeEventEstrus := &EstrusStartData{}
  242. if err := e.DB.Model(new(model.NeckRingEstrus)).
  243. Select("cow_id,MIN(STR_TO_DATE(first_time, '%Y-%m-%d %H:%i:%s')) as first_time").
  244. Where("active_time BETWEEN ? AND ?",
  245. fmt.Sprintf("%s 00:00:00", xToday.AddDate(0, 0, -1).Format(model.LayoutDate2)),
  246. fmt.Sprintf("%s 23:59:59", xToday.Format(model.LayoutDate2)),
  247. ).Where("pasture_id = ?", pastureId).
  248. Where("cow_id = ?", cowId).
  249. First(&firstTimeEventEstrus).Error; err != nil {
  250. return nil
  251. }
  252. return firstTimeEventEstrus
  253. }
  254. // calculateCFT 计算cft值
  255. func calculateCFT(habit *model.NeckActiveHabit) (cft float32) {
  256. if habit.ChangeAdjust >= 10 {
  257. cft = (float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3) * float32(habit.FilterCorrect) / 100
  258. } else {
  259. cft = float32(habit.ChangeFilter) * float32(habit.FilterCorrect) / 100
  260. }
  261. switch {
  262. case habit.RuminaFilter > MaxRuminaAdJust:
  263. cft -= float32(5)
  264. case habit.RuminaFilter > 0:
  265. cft -= float32(habit.RuminaFilter) * 0.25
  266. case habit.RuminaFilter < -MaxRuminaAdJust:
  267. cft -= -MaxRuminaAdJust * RumtoHeat
  268. default:
  269. cft -= float32(habit.RuminaFilter) * RumtoHeat
  270. }
  271. return cft
  272. }
  273. // calculateActiveLevel 计算活动量等级
  274. func calculateActiveLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
  275. if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle {
  276. return pasturePb.EstrusLevel_Low
  277. } else if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh {
  278. return pasturePb.EstrusLevel_Middle
  279. } else {
  280. return pasturePb.EstrusLevel_High
  281. }
  282. }
  283. // getResult 根据b3数据计算结果
  284. func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
  285. result := pasturePb.CheckResult_Pending
  286. if b3.CheckResult == pasturePb.CheckResult_Correct {
  287. result = pasturePb.CheckResult_Correct
  288. }
  289. if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
  290. result = pasturePb.CheckResult_Fail
  291. }
  292. return result
  293. }