neck_ring_estrus.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. package crontab
  2. import (
  3. "fmt"
  4. "kpt-pasture/model"
  5. "kpt-pasture/util"
  6. "sort"
  7. "time"
  8. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  9. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  10. "go.uber.org/zap"
  11. "gitee.com/xuyiping_admin/pkg/xerr"
  12. )
  13. const (
  14. MaxRuminaAdJust = 20
  15. XAdjust21 = 15
  16. XAdjust42 = 10
  17. RumtoHeat = 0.5
  18. MinCalvingAge = 20
  19. MinLact = 0
  20. NormalChangJust = 10
  21. B48 = 48
  22. )
  23. func (e *Entry) UpdateCowEstrus() (err error) {
  24. pastureList := e.FindPastureList()
  25. if pastureList == nil || len(pastureList) == 0 {
  26. return nil
  27. }
  28. for _, pasture := range pastureList {
  29. if err = e.EntryCowEstrus(pasture.Id); err != nil {
  30. zaplog.Error("EntryCrontab", zap.Any("PastureUpdateCowEstrus", err), zap.Any("pasture", pasture))
  31. }
  32. zaplog.Info("PastureUpdateCowEstrus-success", zap.Any("pasture", pasture.Id))
  33. }
  34. return nil
  35. }
  36. func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
  37. xToday := &XToday{}
  38. systemConfigureList, err := e.FindSystemNeckRingConfigure(pastureId)
  39. if err != nil {
  40. return xerr.WithStack(err)
  41. }
  42. if systemConfigureList == nil || len(systemConfigureList) == 0 {
  43. return nil
  44. }
  45. for _, v := range systemConfigureList {
  46. switch v.Name {
  47. case model.ActiveLow:
  48. xToday.ActiveLow = int32(v.Value)
  49. case model.ActiveMiddle:
  50. xToday.ActiveMiddle = int32(v.Value)
  51. case model.ActiveHigh:
  52. xToday.ActiveHigh = int32(v.Value)
  53. }
  54. }
  55. nowTime := time.Now().Local()
  56. e.CowEstrusWarning(pastureId, xToday, nowTime)
  57. e.UpdateNewNeckRingEstrus(pastureId, xToday, nowTime)
  58. return nil
  59. }
  60. // CowEstrusWarning 发情预警
  61. func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.Time) {
  62. cft := xToday.ActiveLow - XAdjust21
  63. neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  64. habitStartDate := nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2)
  65. habitEndDate := nowTime.Format(model.LayoutDate2)
  66. if err := e.DB.Model(new(model.NeckActiveHabit)).
  67. Where("heat_date BETWEEN ? AND ?", habitStartDate, habitEndDate). // todo 查询的时间范围可以优化成当前时间的前12个小时的
  68. Where("pasture_id = ?", pastureId).
  69. Where("filter_high > 0 AND change_filter > ?", model.DefaultChangeFilter).
  70. Where("cow_id > ?", 0).
  71. Where("cft >= ?", cft).
  72. Where(e.DB.Where("calving_age >= ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛
  73. Order("cow_id").
  74. Find(&neckActiveHabitList).Error; err != nil {
  75. zaplog.Error("CowEstrusWarning", zap.Any("Find", err))
  76. return
  77. }
  78. neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit)
  79. for _, habit := range neckActiveHabitList {
  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. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  86. for cowId, cowHabitList := range neckActiveHabitMap {
  87. // 最近3天最大发情记录,小于该变化趋势的不再插入
  88. before3StartDate := nowTime.AddDate(0, 0, -2).Format(model.LayoutTime)
  89. before3Data := e.GetBeforeThreeDaysCowEstrus(pastureId, cowId, before3StartDate)
  90. // 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42
  91. cow21EstrusStartDate := nowTime.AddDate(0, 0, -100).Format(model.LayoutTime)
  92. cow21EstrusEndDate := nowTime.AddDate(0, 0, -2).Format(model.LayoutTime)
  93. cow21Estrus := e.GetTwoEstrus(pastureId, cowId, cow21EstrusStartDate, cow21EstrusEndDate)
  94. if cow21Estrus.ActiveDate != "" {
  95. activeDateTime, _ := util.TimeParseLocal(model.LayoutTime, cow21Estrus.ActiveDate)
  96. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() {
  97. cow21Estrus.HadJust = XAdjust21
  98. }
  99. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -50).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -36).Unix() {
  100. cow21Estrus.HadJust = XAdjust42
  101. }
  102. }
  103. maxCft := float32(0)
  104. maxHigh := int32(0)
  105. lastActiveDate := time.Time{}
  106. for _, habit := range cowHabitList {
  107. if habit.Cft > maxCft {
  108. maxCft = habit.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, err := e.GetCowById(pastureId, cowId)
  127. if err != nil || 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. activeTime := lastActiveDate.Format(model.LayoutTime)
  139. if e.HistoryNeckRingEstrus(pastureId, cowInfo.NeckRingNumber, activeTime) {
  140. continue
  141. }
  142. zaplog.Info("CowEstrusWarning",
  143. zap.Any("level", level),
  144. zap.Any("b48", b48),
  145. zap.Any("checkResult", checkResult),
  146. zap.Any("isShow", isShow),
  147. zap.Any("lastEstrusDate", lastEstrusDate),
  148. zap.Any("activeDate", lastActiveDate),
  149. zap.Any("dayHigh", dayHigh),
  150. zap.Any("maxCft", maxCft),
  151. zap.Any("before3Data", before3Data),
  152. zap.Any("cowEstrus", cow21Estrus),
  153. zap.Any("cowInfo", cowInfo),
  154. zap.Any("cowHabitList", cowHabitList),
  155. zap.Any("pasture", pastureId),
  156. )
  157. newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, level, int32(maxCft), checkResult, isShow)
  158. newNeckRingEstrus.LastTime = lastEstrusDate
  159. newNeckRingEstrus.ActiveTime = activeTime
  160. newNeckRingEstrus.DayHigh = dayHigh
  161. newNeckRingEstrus.MaxHigh = maxHigh
  162. newNeckRingEstrus.IsPeak = pasturePb.IsShow_No
  163. neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
  164. }
  165. }
  166. zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList))
  167. if len(neckRingEstrusList) > 0 {
  168. if err := e.DB.Model(new(model.NeckRingEstrus)).
  169. Create(neckRingEstrusList).Error; err != nil {
  170. zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
  171. }
  172. }
  173. }
  174. // UpdateNewNeckRingEstrus 更新牛只首次发情时间和是否是高峰期
  175. func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, xToday *XToday, nowTime time.Time) {
  176. e.UpdateEstrusFirstTime1(pastureId)
  177. e.UpdateEstrusFirstTime2(pastureId, xToday)
  178. e.UpdateEstrusFirstTime3(pastureId, nowTime)
  179. e.UpdateEstrusIsPeak(pastureId)
  180. }
  181. // UpdateEstrusFirstTime1 更新牛只首次发情时间
  182. func (e *Entry) UpdateEstrusFirstTime1(pastureId int64) {
  183. // 获取牛只首次发情时间为空的记录
  184. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  185. zaplog.Info("UpdateEstrusFirstTime1", zap.Any("neckRingEstrusList", neckRingEstrusList))
  186. for _, v := range neckRingEstrusList {
  187. cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v)
  188. zaplog.Info("UpdateEstrusFirstTime1", zap.Any("cowEstrusStartData", cowEstrusStartData))
  189. if cowEstrusStartData != nil && cowEstrusStartData.FirstTime != "" {
  190. if err := e.DB.Model(new(model.NeckRingEstrus)).
  191. Where("id = ?", v.Id).
  192. Update("first_time", cowEstrusStartData.FirstTime).Error; err != nil {
  193. zaplog.Error("UpdateEstrusFirstTime1",
  194. zap.Any("v", v),
  195. zap.Any("err", err),
  196. zap.Any("cowEstrusStartData", cowEstrusStartData),
  197. )
  198. }
  199. }
  200. }
  201. }
  202. func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday *XToday) {
  203. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  204. for _, v := range neckRingEstrusList {
  205. if v.FirstTime != "" {
  206. continue
  207. }
  208. // 获取牛只最近12小时内的活动记录
  209. activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
  210. startTime := activeTime.Add(-12 * time.Hour)
  211. // 查询符合条件的活动记录
  212. var firstTime string
  213. if err := e.DB.Model(new(model.NeckActiveHabit)).
  214. Select("MIN(active_time) as first_time").
  215. Where("pasture_id = ?", pastureId).
  216. Where("cow_id = ?", v.CowId).
  217. Where("active_time BETWEEN ? AND ?", startTime.Format(model.LayoutTime), v.ActiveTime).
  218. Where("cft >= ?", xToday.ActiveLow).
  219. Scan(&firstTime).Error; err != nil {
  220. zaplog.Error("UpdateEstrusFirstTime2", zap.Any("FindFirstTime", err))
  221. continue
  222. }
  223. if firstTime != "" {
  224. if err := e.DB.Model(new(model.NeckRingEstrus)).
  225. Where("id = ?", v.Id).
  226. Update("first_time", firstTime).Error; err != nil {
  227. zaplog.Error("UpdateEstrusFirstTime2", zap.Any("Update", err))
  228. }
  229. }
  230. }
  231. }
  232. func (e *Entry) UpdateEstrusFirstTime3(pastureId int64, xToday time.Time) {
  233. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  234. for _, v := range neckRingEstrusList {
  235. activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
  236. if activeTime.After(xToday.AddDate(0, 0, -2)) {
  237. if err := e.DB.Model(new(model.NeckRingEstrus)).
  238. Where("id = ?", v.Id).
  239. Update("first_time", v.ActiveTime).Error; err != nil {
  240. zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err))
  241. }
  242. }
  243. }
  244. }
  245. func (e *Entry) UpdateEstrusIsPeak(pastureId int64) {
  246. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  247. nowTime := time.Now().Local()
  248. firstTime := fmt.Sprintf("%s 00:00:00", nowTime.AddDate(0, 0, -3).Format(model.LayoutDate2))
  249. if err := e.DB.Model(new(model.NeckRingEstrus)).
  250. Where("first_time >= ?", firstTime).
  251. Where("active_time != ?", "").
  252. Where("pasture_id = ?", pastureId).
  253. Find(&neckRingEstrusList).Error; err != nil {
  254. zaplog.Error("UpdateEstrusIsPeak", zap.Any("Find", err))
  255. }
  256. if len(neckRingEstrusList) <= 0 {
  257. return
  258. }
  259. neckRingEstrusFirstMap := make(map[string][]*model.NeckRingEstrus)
  260. for _, v := range neckRingEstrusList {
  261. prefix := fmt.Sprintf("%s_%d", v.FirstTime, v.CowId)
  262. if neckRingEstrusFirstMap[prefix] == nil {
  263. neckRingEstrusFirstMap[prefix] = make([]*model.NeckRingEstrus, 0)
  264. }
  265. neckRingEstrusFirstMap[prefix] = append(neckRingEstrusFirstMap[prefix], v)
  266. }
  267. peakIsShow := make([]int64, 0)
  268. for _, estrusItems := range neckRingEstrusFirstMap {
  269. eLen := len(estrusItems)
  270. if eLen <= 0 {
  271. continue
  272. }
  273. // 倒序排序
  274. sort.Slice(estrusItems, func(i, j int) bool {
  275. return estrusItems[i].ActiveTime < estrusItems[j].ActiveTime
  276. })
  277. // 判断是否是高峰期,和当前时间相比,如果超过2个小就是高峰期
  278. lastItem := estrusItems[eLen-1]
  279. lastActiveTime := util.DateTimeParseLocalUnix2(lastItem.ActiveTime)
  280. sub := nowTime.Sub(lastActiveTime).Hours()
  281. if sub > 4 && lastItem.IsPeak == pasturePb.IsShow_No {
  282. peakIsShow = append(peakIsShow, lastItem.Id)
  283. }
  284. zaplog.Info("UpdateEstrusIsPeak01",
  285. zap.Any("pastureId", pastureId),
  286. zap.Any("estrusItems", estrusItems),
  287. zap.Any("peakIsShow", peakIsShow),
  288. zap.Any("lastActiveTime", lastActiveTime),
  289. zap.Any("sub", sub),
  290. zap.Any("nowTime", nowTime.Format(model.LayoutTime)),
  291. )
  292. }
  293. if len(peakIsShow) > 0 {
  294. if err := e.DB.Model(new(model.NeckRingEstrus)).
  295. Where("id IN ?", peakIsShow).
  296. Where("pasture_id = ?", pastureId).
  297. Update("is_peak", pasturePb.IsShow_Ok).Error; err != nil {
  298. zaplog.Error("UpdateEstrusIsPeak", zap.Any("Update", err))
  299. }
  300. }
  301. }
  302. // FindCowEstrusFirstTime1 查找牛只昨天是否有发情数据
  303. func (e *Entry) FindCowEstrusFirstTime1(pastureId int64, neckRingEstrus *model.NeckRingEstrus) *EstrusStartData {
  304. firstTimeEventEstrus := &EstrusStartData{}
  305. activeAt := util.DateTimeParseLocalUnix2(neckRingEstrus.ActiveTime)
  306. startDate := fmt.Sprintf("%s 00:00:00", activeAt.AddDate(0, 0, -1).Format(model.LayoutDate2))
  307. if err := e.DB.Model(new(model.NeckRingEstrus)).
  308. Select("cow_id,first_time").
  309. Where("active_time BETWEEN ? AND ?", startDate, neckRingEstrus.ActiveTime).
  310. Where("pasture_id = ?", pastureId).
  311. Where("cow_id = ?", neckRingEstrus.CowId).
  312. Where("id != ?", neckRingEstrus.Id).
  313. Order("first_time,id ASC").
  314. First(&firstTimeEventEstrus).Error; err != nil {
  315. return nil
  316. }
  317. return firstTimeEventEstrus
  318. }
  319. func (e *Entry) HistoryNeckRingEstrus(pastureId int64, neckRingNumber string, activeTime string) bool {
  320. var count int64
  321. if err := e.DB.Model(new(model.NeckRingEstrus)).
  322. Where("pasture_id = ?", pastureId).
  323. Where("neck_ring_number = ?", neckRingNumber).
  324. Where("active_time = ?", activeTime).
  325. Count(&count).Error; err != nil {
  326. return false
  327. }
  328. return count > 0
  329. }
  330. // CalculateCFT 计算cft值
  331. func CalculateCFT(habit *model.NeckActiveHabit) (cft float32) {
  332. if habit.ChangeAdjust >= 10 {
  333. cft = (float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3) * float32(habit.FilterCorrect) / 100
  334. } else {
  335. cft = float32(habit.ChangeFilter) * float32(habit.FilterCorrect) / 100
  336. }
  337. switch {
  338. case habit.RuminaFilter > MaxRuminaAdJust:
  339. cft -= float32(5)
  340. case habit.RuminaFilter > 0:
  341. cft -= float32(habit.RuminaFilter) * 0.25
  342. case habit.RuminaFilter < -MaxRuminaAdJust:
  343. cft -= -MaxRuminaAdJust * RumtoHeat
  344. default:
  345. cft -= float32(habit.RuminaFilter) * RumtoHeat
  346. }
  347. return cft
  348. }
  349. // calculateActiveLevel 计算活动量等级
  350. func calculateActiveLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
  351. if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle {
  352. return pasturePb.EstrusLevel_Low
  353. } else if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh {
  354. return pasturePb.EstrusLevel_Middle
  355. } else {
  356. return pasturePb.EstrusLevel_High
  357. }
  358. }
  359. // getResult 根据b3数据计算结果
  360. func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
  361. result := pasturePb.CheckResult_Pending
  362. if b3.CheckResult == pasturePb.CheckResult_Correct {
  363. result = pasturePb.CheckResult_Correct
  364. }
  365. if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
  366. result = pasturePb.CheckResult_Fail
  367. }
  368. return result
  369. }