neck_ring_estrus.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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.FindSystemNeckRingConfigure(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().Local()
  55. e.CowEstrusWarning(pastureId, xToday, nowTime)
  56. e.UpdateNewNeckRingEstrus(pastureId, xToday, nowTime)
  57. return nil
  58. }
  59. // CowEstrusWarning 发情预警
  60. func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.Time) {
  61. cft := xToday.ActiveLow - XAdjust21
  62. neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  63. if err := e.DB.Model(new(model.NeckActiveHabit)).
  64. Where("heat_date BETWEEN ? AND ?", nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2), 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("cft >= ?", cft).
  69. Where(e.DB.Where("calving_age >= ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛
  70. Order("cow_id").
  71. Find(&neckActiveHabitList).Error; err != nil {
  72. zaplog.Error("CowEstrusWarning", zap.Any("Find", err))
  73. return
  74. }
  75. neckActiveHabitMap := make(map[int64][]*model.NeckActiveHabit)
  76. for _, habit := range neckActiveHabitList {
  77. zaplog.Info("CowEstrusWarning", zap.Any("habit", habit))
  78. if neckActiveHabitMap[habit.CowId] == nil {
  79. neckActiveHabitMap[habit.CowId] = make([]*model.NeckActiveHabit, 0)
  80. }
  81. neckActiveHabitMap[habit.CowId] = append(neckActiveHabitMap[habit.CowId], habit)
  82. }
  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. cow21Estrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
  89. if cow21Estrus.ActiveDate != "" {
  90. activeDateTime, _ := util.TimeParseLocal(model.LayoutTime, cow21Estrus.ActiveDate)
  91. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() {
  92. cow21Estrus.HadJust = XAdjust21
  93. }
  94. if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -50).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -36).Unix() {
  95. cow21Estrus.HadJust = XAdjust42
  96. }
  97. }
  98. maxCft := float32(0)
  99. maxHigh := int32(0)
  100. lastActiveDate := time.Time{}
  101. for _, habit := range cowHabitList {
  102. if habit.Cft > maxCft {
  103. maxCft = habit.Cft
  104. }
  105. if habit.FilterHigh > maxHigh {
  106. maxHigh = habit.FilterHigh
  107. }
  108. // 获取最新的 CreateTime
  109. activeTimeParse, _ := util.TimeParseLocal(model.LayoutTime, habit.ActiveTime)
  110. if activeTimeParse.After(lastActiveDate) {
  111. lastActiveDate = activeTimeParse
  112. }
  113. }
  114. b48 := float64(0)
  115. if len(before3Data.ActiveTime) > 0 {
  116. t3, _ := util.TimeParseLocal(model.LayoutTime, before3Data.ActiveTime)
  117. b48 = t3.Sub(lastActiveDate).Hours()
  118. }
  119. if (int32(maxCft) > before3Data.DayHigh || before3Data.CowId == 0 || b48 > B48) && int32(maxCft)+cow21Estrus.HadJust > xToday.ActiveLow {
  120. level := calculateActiveLevel(maxCft, cow21Estrus, xToday)
  121. cowInfo, err := e.GetCowById(pastureId, cowId)
  122. if err != nil || 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) + cow21Estrus.HadJust
  131. lastEstrusDate := cow21Estrus.ActiveDate
  132. checkResult := getResult(before3Data, maxCft, cow21Estrus)
  133. isPeak := pasturePb.IsShow_Ok
  134. activeTime := lastActiveDate.Format(model.LayoutTime)
  135. if e.HistoryNeckRingEstrus(pastureId, cowInfo.NeckRingNumber, activeTime) {
  136. continue
  137. }
  138. zaplog.Info("CowEstrusWarning",
  139. zap.Any("level", level),
  140. zap.Any("b48", b48),
  141. zap.Any("checkResult", checkResult),
  142. zap.Any("isShow", isShow),
  143. zap.Any("isPeak", isPeak),
  144. zap.Any("lastEstrusDate", lastEstrusDate),
  145. zap.Any("activeDate", lastActiveDate),
  146. zap.Any("dayHigh", dayHigh),
  147. zap.Any("maxCft", maxCft),
  148. zap.Any("before3Data", before3Data),
  149. zap.Any("cowEstrus", cow21Estrus),
  150. zap.Any("cowInfo", cowInfo),
  151. zap.Any("cowHabitList", cowHabitList),
  152. )
  153. newNeckRingEstrus := model.NewNeckRingEstrus(pastureId, cowInfo, level, int32(maxCft), checkResult, isShow)
  154. newNeckRingEstrus.LastTime = lastEstrusDate
  155. newNeckRingEstrus.ActiveTime = activeTime
  156. newNeckRingEstrus.DayHigh = dayHigh
  157. newNeckRingEstrus.MaxHigh = maxHigh
  158. newNeckRingEstrus.IsPeak = isPeak
  159. neckRingEstrusList = append(neckRingEstrusList, newNeckRingEstrus)
  160. }
  161. }
  162. zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList))
  163. if len(neckRingEstrusList) > 0 {
  164. if err := e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
  165. zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
  166. }
  167. }
  168. }
  169. func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, xToday *XToday, nowTime time.Time) {
  170. // 更新牛只首次发情时间
  171. e.UpdateEstrusFirstTime1(pastureId, nowTime)
  172. e.UpdateEstrusIsPeak(pastureId, nowTime)
  173. e.UpdateEstrusFirstTime2(pastureId, xToday, nowTime)
  174. e.UpdateEstrusFirstTime3(pastureId, nowTime)
  175. }
  176. // UpdateEstrusFirstTime1 更新牛只首次发情时间
  177. func (e *Entry) UpdateEstrusFirstTime1(pastureId int64, xToday time.Time) {
  178. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  179. for _, v := range neckRingEstrusList {
  180. cowEstrusStartData := e.FindCowEstrusFirstTime1(pastureId, v.CowId, xToday)
  181. if cowEstrusStartData != nil && cowEstrusStartData.FirstTime != "" {
  182. if err := e.DB.Model(new(model.NeckRingEstrus)).
  183. Where("id = ?", v.Id).
  184. Update("first_time", cowEstrusStartData.FirstTime).Error; err != nil {
  185. zaplog.Error("UpdateEstrusFirstTime1",
  186. zap.Any("v", v),
  187. zap.Any("err", err),
  188. zap.Any("cowEstrusStartData", cowEstrusStartData),
  189. )
  190. }
  191. }
  192. }
  193. }
  194. func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday *XToday, nowTime time.Time) {
  195. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  196. for _, v := range neckRingEstrusList {
  197. // 获取牛只最近12小时内的活动记录
  198. activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
  199. startTime := activeTime.Add(-12 * time.Hour)
  200. // 查询符合条件的活动记录
  201. var firstTime string
  202. if err := e.DB.Model(new(model.NeckActiveHabit)).
  203. Select("MIN(active_time) as first_time").
  204. Where("pasture_id = ?", pastureId).
  205. Where("cow_id = ?", v.CowId).
  206. Where("heat_date = ?", activeTime.Format(model.LayoutDate2)).
  207. Where("active_time BETWEEN ? AND ?", startTime.Format(model.LayoutTime), v.ActiveTime).
  208. Where("cft >= ?", xToday.ActiveLow).
  209. Scan(&firstTime).Error; err != nil {
  210. zaplog.Error("UpdateEstrusFirstTime2", zap.Any("FindFirstTime", err))
  211. continue
  212. }
  213. if firstTime != "" {
  214. if err := e.DB.Model(new(model.NeckRingEstrus)).
  215. Where("id = ?", v.Id).
  216. Update("first_time", firstTime).Error; err != nil {
  217. zaplog.Error("UpdateEstrusFirstTime2", zap.Any("Update", err))
  218. }
  219. }
  220. }
  221. }
  222. func (e *Entry) UpdateEstrusFirstTime3(pastureId int64, xToday time.Time) {
  223. neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
  224. for _, v := range neckRingEstrusList {
  225. activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
  226. if activeTime.After(xToday.AddDate(0, 0, -2)) {
  227. if err := e.DB.Model(new(model.NeckRingEstrus)).
  228. Where("id = ?", v.Id).
  229. Update("first_time", v.ActiveTime).Error; err != nil {
  230. zaplog.Error("UpdateEstrusFirstTime1", zap.Any("v", v), zap.Any("err", err))
  231. }
  232. }
  233. }
  234. }
  235. func (e *Entry) UpdateEstrusIsPeak(pastureId int64, xToday time.Time) {
  236. neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
  237. if err := e.DB.Model(new(model.NeckRingEstrus)).
  238. Where("first_time != ?", "").
  239. Where("is_peak = ?", pasturePb.IsShow_Ok).
  240. Where("is_show = ?", pasturePb.IsShow_Ok).
  241. Where("pasture_id = ?", pastureId).
  242. Find(&neckRingEstrusList).Error; err != nil {
  243. zaplog.Error("UpdateEstrusIsPeak", zap.Any("Find", err))
  244. }
  245. for _, estrus := range neckRingEstrusList {
  246. activeTime, _ := util.TimeParseLocal(model.LayoutTime, estrus.ActiveTime)
  247. if activeTime.Before(xToday) || activeTime.After(xToday.AddDate(0, 0, 1)) {
  248. continue
  249. }
  250. var exists bool
  251. if err := e.DB.Model(new(model.NeckRingEstrus)).
  252. Where("cow_id = ?", estrus.CowId).
  253. Where("first_time != ?", "").
  254. Where("active_time BETWEEN ? AND ?", xToday, xToday.AddDate(0, 0, 1)).
  255. Where("active_time BETWEEN ? AND ?", estrus.FirstTime, activeTime.Add(-2*time.Hour)).
  256. Select("1").
  257. Limit(1).
  258. Scan(&exists).Error; err != nil {
  259. zaplog.Error("UpdateEstrusIsPeak", zap.Any("exists", err))
  260. continue
  261. }
  262. if exists {
  263. if err := e.DB.Model(estrus).
  264. Update("is_peak", pasturePb.IsShow_No).Error; err != nil {
  265. zaplog.Error("UpdateEstrusIsPeak", zap.Any("v", estrus), zap.Any("err", err))
  266. }
  267. }
  268. }
  269. }
  270. // FindCowEstrusFirstTime1 查找牛只昨天是否有发情数据
  271. func (e *Entry) FindCowEstrusFirstTime1(pastureId, cowId int64, xToday time.Time) *EstrusStartData {
  272. firstTimeEventEstrus := &EstrusStartData{}
  273. if err := e.DB.Model(new(model.NeckRingEstrus)).
  274. Select("cow_id,MIN(STR_TO_DATE(first_time, '%Y-%m-%d %H:%i:%s')) as first_time").
  275. Where("active_time BETWEEN ? AND ?",
  276. fmt.Sprintf("%s 00:00:00", xToday.AddDate(0, 0, -1).Format(model.LayoutDate2)),
  277. fmt.Sprintf("%s 23:59:59", xToday.Format(model.LayoutDate2)),
  278. ).Where("pasture_id = ?", pastureId).
  279. Where("cow_id = ?", cowId).
  280. First(&firstTimeEventEstrus).Error; err != nil {
  281. return nil
  282. }
  283. return firstTimeEventEstrus
  284. }
  285. func (e *Entry) HistoryNeckRingEstrus(pastureId int64, neckRingNumber string, activeTime string) bool {
  286. var count int64
  287. if err := e.DB.Model(new(model.NeckRingEstrus)).
  288. Where("pasture_id = ?", pastureId).
  289. Where("neck_ring_number = ?", neckRingNumber).
  290. Where("active_time = ?", activeTime).
  291. Count(&count).Error; err != nil {
  292. return false
  293. }
  294. return count > 0
  295. }
  296. // CalculateCFT 计算cft值
  297. func CalculateCFT(habit *model.NeckActiveHabit) (cft float32) {
  298. if habit.ChangeAdjust >= 10 {
  299. cft = (float32(habit.ChangeFilter) - float32(habit.ChangeAdjust) + 3) * float32(habit.FilterCorrect) / 100
  300. } else {
  301. cft = float32(habit.ChangeFilter) * float32(habit.FilterCorrect) / 100
  302. }
  303. switch {
  304. case habit.RuminaFilter > MaxRuminaAdJust:
  305. cft -= float32(5)
  306. case habit.RuminaFilter > 0:
  307. cft -= float32(habit.RuminaFilter) * 0.25
  308. case habit.RuminaFilter < -MaxRuminaAdJust:
  309. cft -= -MaxRuminaAdJust * RumtoHeat
  310. default:
  311. cft -= float32(habit.RuminaFilter) * RumtoHeat
  312. }
  313. return cft
  314. }
  315. // calculateActiveLevel 计算活动量等级
  316. func calculateActiveLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb.EstrusLevel_Kind {
  317. if int32(cft)+cowEstrus.HadJust < xToday.ActiveMiddle {
  318. return pasturePb.EstrusLevel_Low
  319. } else if int32(cft)+cowEstrus.HadJust >= xToday.ActiveHigh {
  320. return pasturePb.EstrusLevel_Middle
  321. } else {
  322. return pasturePb.EstrusLevel_High
  323. }
  324. }
  325. // getResult 根据b3数据计算结果
  326. func getResult(b3 *model.NeckRingEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
  327. result := pasturePb.CheckResult_Pending
  328. if b3.CheckResult == pasturePb.CheckResult_Correct {
  329. result = pasturePb.CheckResult_Correct
  330. }
  331. if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
  332. result = pasturePb.CheckResult_Fail
  333. }
  334. return result
  335. }