neck_ring_estrus.go 12 KB

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