cow.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. package backend
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "kpt-pasture/model"
  7. "kpt-pasture/util"
  8. "net/http"
  9. "sort"
  10. "strings"
  11. "sync"
  12. "time"
  13. "gorm.io/gorm"
  14. "gitee.com/xuyiping_admin/pkg/xerr"
  15. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  16. )
  17. func (s *StoreEntry) Detail(ctx context.Context, req *pasturePb.SearchEventRequest) (*pasturePb.CowInfoResponse, error) {
  18. userModel, err := s.GetUserModel(ctx)
  19. if err != nil {
  20. return nil, xerr.WithStack(err)
  21. }
  22. if req.EarNumber == "" && req.NeckRingNumber == "" {
  23. return nil, xerr.Custom("请输入牛号或项圈号")
  24. }
  25. cowInfo := &model.Cow{}
  26. pref := s.DB.Model(new(model.Cow)).
  27. Where("pasture_id = ?", userModel.AppPasture.Id)
  28. if req.EarNumber != "" {
  29. pref.Where("ear_number = ?", req.EarNumber)
  30. }
  31. if req.NeckRingNumber != "" {
  32. pref.Where("neck_ring_number = ?", req.NeckRingNumber)
  33. }
  34. if err = pref.Order("id desc").
  35. First(cowInfo).Error; err != nil {
  36. if errors.Is(err, gorm.ErrRecordNotFound) {
  37. return nil, xerr.Custom("该牛只未找到")
  38. } else {
  39. return nil, xerr.WithStack(err)
  40. }
  41. }
  42. cowTypeMap := s.CowTypeMap()
  43. breedStatusMap := s.CowBreedStatusMap()
  44. cowKindMap := s.CowKindMap()
  45. cowSourceMap := s.CowSourceMap()
  46. admissionStatusMap := s.AdmissionStatusMap()
  47. healthStatusMap := s.HealthStatusMap()
  48. purposeMap := s.PurposeMap()
  49. systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, model.PregnancyAge)
  50. if err != nil {
  51. return nil, xerr.Custom("请在基础参数配置妊娠天数")
  52. }
  53. cowDetails := model.CowSlice([]*model.Cow{cowInfo}).ToPB(
  54. cowTypeMap, breedStatusMap, cowKindMap, cowSourceMap,
  55. admissionStatusMap, healthStatusMap, purposeMap, systemBasic.MinValue,
  56. )
  57. if len(cowDetails) != 1 {
  58. return nil, xerr.Custom("该牛只未找到")
  59. }
  60. data := cowDetails[0]
  61. return &pasturePb.CowInfoResponse{
  62. Code: http.StatusOK,
  63. Msg: "ok",
  64. Data: data,
  65. }, nil
  66. }
  67. func (s *StoreEntry) List(ctx context.Context, req *pasturePb.SearchEventRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchCowListResponse, error) {
  68. userModel, err := s.GetUserModel(ctx)
  69. if err != nil {
  70. return nil, xerr.WithStack(err)
  71. }
  72. systemBasic, err := s.GetSystemBasicByName(ctx, userModel.AppPasture.Id, model.PregnancyAge)
  73. if err != nil {
  74. return nil, xerr.Custom("请在基础参数配置妊娠天数")
  75. }
  76. cowList := make([]*model.Cow, 0)
  77. var count int64 = 0
  78. pref := s.DB.Model(new(model.Cow)).
  79. Where("pasture_id = ?", userModel.AppPasture.Id)
  80. if len(req.CowId) > 0 {
  81. cowIds := strings.Split(req.CowId, ",")
  82. pref.Where("id IN ?", cowIds)
  83. }
  84. if req.EarNumber != "" {
  85. pref.Where("ear_number like ?", fmt.Sprintf("%s%s%s", "%", req.EarNumber, "%"))
  86. }
  87. if req.Id > 0 {
  88. pref.Where("id = ?", req.Id)
  89. }
  90. if req.PenId > 0 {
  91. pref.Where("pen_id = ?", req.PenId)
  92. }
  93. if req.CowType > 0 {
  94. pref.Where("cow_type = ?", req.CowType)
  95. }
  96. if req.BreedStatus > 0 {
  97. pref.Where("breed_status = ?", req.BreedStatus)
  98. }
  99. if req.CowKind > 0 {
  100. pref.Where("cow_kind = ?", req.CowKind)
  101. }
  102. if req.Sex > 0 {
  103. pref.Where("sex = ?", req.Sex)
  104. }
  105. if req.Lact > 0 {
  106. pref.Where("lact = ?", req.Lact)
  107. }
  108. if req.CowSource > 0 {
  109. pref.Where("source_id = ?", req.CowSource)
  110. }
  111. if err = pref.Order("id desc").
  112. Count(&count).
  113. Limit(int(pagination.PageSize)).
  114. Offset(int(pagination.PageOffset)).
  115. Find(&cowList).Error; err != nil {
  116. return nil, xerr.WithStack(err)
  117. }
  118. cowTypeMap := s.CowTypeMap()
  119. breedStatusMap := s.CowBreedStatusMap()
  120. cowKindMap := s.CowKindMap()
  121. cowSourceMap := s.CowSourceMap()
  122. admissionStatusMap := s.AdmissionStatusMap()
  123. healthStatusMap := s.HealthStatusMap()
  124. purposeMap := s.PurposeMap()
  125. return &pasturePb.SearchCowListResponse{
  126. Code: http.StatusOK,
  127. Msg: "ok",
  128. Data: &pasturePb.SearchCowData{
  129. List: model.CowSlice(cowList).ToPB(
  130. cowTypeMap, breedStatusMap, cowKindMap, cowSourceMap,
  131. admissionStatusMap, healthStatusMap, purposeMap, systemBasic.MinValue,
  132. ),
  133. Total: int32(count),
  134. PageSize: pagination.PageSize,
  135. Page: pagination.Page,
  136. },
  137. }, nil
  138. }
  139. func (s *StoreEntry) EventList(ctx context.Context, req *pasturePb.SearchCowEventListRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CowEventListResponse, error) {
  140. userModel, err := s.GetUserModel(ctx)
  141. if err != nil {
  142. return nil, xerr.WithStack(err)
  143. }
  144. eventCowLogList := make([]*model.EventCowLog, 0)
  145. cowInfo, err := s.GetCowEventByEarNumber(ctx, userModel.AppPasture.Id, req.EarNumber)
  146. if err != nil {
  147. return nil, xerr.Customf("错误的牛只信息: %s", req.EarNumber)
  148. }
  149. eventCowLog := &model.EventCowLog{CowId: cowInfo.Id}
  150. pref := s.DB.Table(eventCowLog.TableName()).
  151. Where("pasture_id = ?", userModel.AppPasture.Id).
  152. Where("cow_id = ?", cowInfo.Id)
  153. if req.Lact >= 0 {
  154. pref.Where("lact = ?", req.Lact)
  155. }
  156. if req.EventCategoryKind > 0 {
  157. pref.Where("event_type = ?", req.EventCategoryKind)
  158. }
  159. if err = pref.Order("event_at DESC").
  160. Limit(int(pagination.PageSize)).
  161. Offset(int(pagination.PageOffset)).
  162. Find(&eventCowLogList).Error; err != nil {
  163. return nil, xerr.WithStack(err)
  164. }
  165. eventCategoryMap := s.EventCategoryMap()
  166. return &pasturePb.CowEventListResponse{
  167. Code: http.StatusOK,
  168. Msg: "ok",
  169. Data: &pasturePb.CowEventData{
  170. List: model.EventCowLogSlice(eventCowLogList).ToPB(eventCategoryMap),
  171. Total: int32(len(eventCowLogList)),
  172. PageSize: pagination.PageSize,
  173. Page: pagination.Page,
  174. },
  175. }, nil
  176. }
  177. func (s *StoreEntry) BehaviorCurve(ctx context.Context, req *pasturePb.CowBehaviorCurveRequest) (*model.CowBehaviorCurveResponse, error) {
  178. userModel, err := s.GetUserModel(ctx)
  179. if err != nil {
  180. return nil, xerr.WithStack(err)
  181. }
  182. cowInfo, err := s.GetCowEventByEarNumber(ctx, userModel.AppPasture.Id, req.EarNumber)
  183. if err != nil {
  184. return nil, xerr.Customf("错误的牛只信息: %d", req.CowId)
  185. }
  186. nowTime := time.Now().Local()
  187. nowDayZero := util.TimeParseLocalUnix(nowTime.Format(model.LayoutDate2))
  188. endDataTime := nowTime.Format(model.LayoutDate2)
  189. startDataTime := nowTime.AddDate(0, 0, -30).Format(model.LayoutDate2)
  190. dayRange, err := util.GetDaysBetween(startDataTime, endDataTime)
  191. if err != nil {
  192. return nil, xerr.WithStack(err)
  193. }
  194. if len(dayRange) <= 0 {
  195. return nil, xerr.Customf("错误的日期范围")
  196. }
  197. // 行为曲线数据
  198. neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  199. if err = s.DB.Table(new(model.NeckActiveHabit).TableName()).
  200. Where("neck_ring_number = ?", cowInfo.NeckRingNumber).
  201. Where("pasture_id = ?", userModel.AppPasture.Id).
  202. Where("is_show = ?", pasturePb.IsShow_Ok).
  203. Where("cow_id > ?", 0).
  204. Where("heat_date IN (?)", dayRange).
  205. Order("heat_date, frameid").
  206. Find(&neckActiveHabitList).Error; err != nil {
  207. return nil, xerr.WithStack(err)
  208. }
  209. data := model.NeckActiveHabitSlice(neckActiveHabitList).ToPB(req.CurveName)
  210. q1, q3 := s.CowIQR(cowInfo, req.CurveName, dayRange, data.DateTimeList)
  211. data.IQR3 = q3
  212. data.IQR1 = q1
  213. eventMapList := s.EventTypeMap()
  214. for k, v := range eventMapList {
  215. if k == pasturePb.EventType_Enter || k == pasturePb.EventType_Body_Score || k == pasturePb.EventType_Birth ||
  216. k == pasturePb.EventType_Weaning || k == pasturePb.EventType_Sale || k == pasturePb.EventType_Weight ||
  217. k == pasturePb.EventType_Castrated {
  218. continue
  219. }
  220. data.EventMap[k] = v
  221. }
  222. data.LowActivity = 62
  223. data.MiddleActivity = 80
  224. // 牛只事件列表
  225. eventLogList := make([]*model.EventCowLog, 0)
  226. eventLog := &model.EventCowLog{CowId: cowInfo.Id}
  227. if err = s.DB.Table(eventLog.TableName()).
  228. Where("cow_id = ?", cowInfo.Id).
  229. Where("pasture_id = ?", userModel.AppPasture.Id).
  230. Where("event_at BETWEEN ? AND ?", nowDayZero-(30*86400), nowDayZero+86400).
  231. Order("event_at").
  232. Find(&eventLogList).Error; err != nil {
  233. return nil, xerr.WithStack(err)
  234. }
  235. for _, v := range eventLogList {
  236. if v.EventType == pasturePb.EventType_Enter || v.EventType == pasturePb.EventType_Body_Score ||
  237. v.EventType == pasturePb.EventType_Birth || v.EventType == pasturePb.EventType_Weaning ||
  238. v.EventType == pasturePb.EventType_Sale || v.EventType == pasturePb.EventType_Weight ||
  239. v.EventType == pasturePb.EventType_Castrated {
  240. continue
  241. }
  242. if v.EventAt <= 0 {
  243. continue
  244. }
  245. eventAt := time.Unix(v.EventAt, 0).Local()
  246. data.EventList = append(data.EventList, &pasturePb.CowEvent{
  247. EventTypeKind: v.EventType,
  248. EventTypeName: v.EventTypeName,
  249. EventDescription: v.EventDescription,
  250. Remarks: v.Remarks,
  251. EventAtFormat: fmt.Sprintf("%s 09", eventAt.Format(model.LayoutDate2)),
  252. })
  253. }
  254. // 发情数据
  255. estrusList := make([]*model.EventEstrus, 0)
  256. if err = s.DB.Table(new(model.EventEstrus).TableName()).
  257. Where("cow_id = ?", cowInfo.Id).
  258. Where("pasture_id = ?", userModel.AppPasture.Id).
  259. Where("reality_day BETWEEN ? AND ?", startDataTime, endDataTime).
  260. Order("reality_day").
  261. Find(&estrusList).Error; err != nil {
  262. return nil, xerr.WithStack(err)
  263. }
  264. for _, v := range estrusList {
  265. if data.EstrusList[v.Level] == nil {
  266. data.EstrusList[v.Level] = make([]string, 0)
  267. }
  268. if v.RealityDay > 0 {
  269. // 格式化为到小时的字符串
  270. hourStr := time.Unix(v.RealityDay, 0).Local().Format(model.LayoutHour)
  271. data.EstrusList[v.Level] = append(data.EstrusList[v.Level], hourStr)
  272. }
  273. }
  274. return &model.CowBehaviorCurveResponse{
  275. Code: http.StatusOK,
  276. Msg: "ok",
  277. Data: data,
  278. }, nil
  279. }
  280. func (s *StoreEntry) CowIQR(cowInfo *model.Cow, curveName string, dayRange []string, dateTimeList []string) ([]int32, []int32) {
  281. q1, q3 := make([]int32, 0), make([]int32, 0)
  282. if curveName == "" || curveName == "active" || curveName == "behavior" || len(dateTimeList) <= 0 {
  283. return q1, q3
  284. }
  285. penId := cowInfo.PenId
  286. cowList := make([]*model.Cow, 0)
  287. if err := s.DB.Table(new(model.Cow).TableName()).
  288. Where("pen_id = ?", penId).
  289. Where("pasture_id = ?", cowInfo.PastureId).
  290. Where("neck_ring_number != ?", "").
  291. Find(&cowList).Error; err != nil {
  292. return q1, q3
  293. }
  294. cowIds := make([]int64, 0)
  295. for _, cow := range cowList {
  296. cowIds = append(cowIds, cow.Id)
  297. }
  298. neckActiveHabitList, err := s.GetNeckActiveHabitsConcurrent(cowInfo, cowIds, dayRange)
  299. if err != nil {
  300. return q1, q3
  301. }
  302. neckActiveHabitMap := make(map[string][]*model.NeckActiveHabit)
  303. for _, habit := range neckActiveHabitList {
  304. activeTime := habit.ActiveTime[:13]
  305. neckActiveHabitMap[activeTime] = append(neckActiveHabitMap[activeTime], habit)
  306. }
  307. penOriginal := make(map[string][]int32)
  308. for _, dt := range dateTimeList {
  309. habits, ok := neckActiveHabitMap[dt]
  310. if !ok {
  311. penOriginal[dt] = append(penOriginal[dt], int32(0))
  312. continue
  313. }
  314. switch curveName {
  315. case "rumina":
  316. for _, habit := range habits {
  317. penOriginal[dt] = append(penOriginal[dt], habit.Rumina)
  318. }
  319. case "intake":
  320. for _, habit := range habits {
  321. penOriginal[dt] = append(penOriginal[dt], habit.Inactive)
  322. }
  323. case "inactive":
  324. for _, habit := range habits {
  325. penOriginal[dt] = append(penOriginal[dt], habit.Inactive)
  326. }
  327. case "chew":
  328. for _, habit := range habits {
  329. penOriginal[dt] = append(penOriginal[dt], habit.Rumina+habit.Intake)
  330. }
  331. case "immobility":
  332. for _, habit := range habits {
  333. penOriginal[dt] = append(penOriginal[dt], 120-habit.Active)
  334. }
  335. }
  336. }
  337. if len(penOriginal) > 0 {
  338. for _, dataList := range penOriginal {
  339. dl := len(dataList)
  340. if dl < 4 {
  341. q1 = append(q1, int32(0))
  342. q3 = append(q3, int32(0))
  343. continue
  344. }
  345. sort.Slice(dataList, func(i, j int) bool {
  346. return dataList[i] < dataList[j]
  347. })
  348. v1 := (len(dataList) + 1) / 4
  349. // 防止下标溢出panic
  350. s1 := float64(dataList[v1-1]) + 0.25*float64(dataList[v1]-dataList[v1-1])
  351. v3 := v1 * 3
  352. s3 := float64(dataList[v3-1]) + 0.75*float64(dataList[v3]-dataList[v3-1])
  353. q1 = append(q1, int32(s1))
  354. q3 = append(q3, int32(s3))
  355. }
  356. }
  357. return q1, q3
  358. }
  359. func (s *StoreEntry) GetNeckActiveHabitsConcurrent(cowInfo *model.Cow, cowIds []int64, dayRange []string) ([]*model.NeckActiveHabit, error) {
  360. const (
  361. batchSize = 100
  362. workerCount = 5
  363. )
  364. resultsChan := make(chan []*model.NeckActiveHabit)
  365. errChan := make(chan error, 1) // 缓冲 1,避免 goroutine 阻塞
  366. doneChan := make(chan struct{})
  367. var (
  368. neckActiveHabitList []*model.NeckActiveHabit
  369. mu sync.Mutex
  370. )
  371. // 结果收集 goroutine
  372. go func() {
  373. defer close(doneChan)
  374. for batchResults := range resultsChan {
  375. mu.Lock()
  376. neckActiveHabitList = append(neckActiveHabitList, batchResults...)
  377. mu.Unlock()
  378. }
  379. }()
  380. sem := make(chan struct{}, workerCount)
  381. var wg sync.WaitGroup
  382. // 分发任务
  383. go func() {
  384. defer func() {
  385. wg.Wait() // 等待所有 goroutine 完成
  386. close(resultsChan) // 安全关闭
  387. }()
  388. for i := 0; i < len(cowIds); i += batchSize {
  389. end := i + batchSize
  390. if end > len(cowIds) {
  391. end = len(cowIds)
  392. }
  393. batch := cowIds[i:end]
  394. wg.Add(1)
  395. sem <- struct{}{} // 获取令牌(可能阻塞,但不会死锁)
  396. go func(batch []int64) {
  397. defer func() {
  398. <-sem // 释放令牌
  399. wg.Done()
  400. }()
  401. var batchResults []*model.NeckActiveHabit
  402. if err := s.DB.Table(new(model.NeckActiveHabit).TableName()).
  403. Select("rumina,intake,inactive,gasp,high,active,active_time").
  404. Where("pasture_id = ?", cowInfo.PastureId).
  405. Where("is_show = ?", pasturePb.IsShow_Ok).
  406. Where("cow_id IN (?)", batch).
  407. Where("heat_date BETWEEN ? AND ?", dayRange[0], dayRange[len(dayRange)-1]).
  408. Find(&batchResults).Error; err != nil {
  409. select {
  410. case errChan <- fmt.Errorf("batch query error: %v", err):
  411. default:
  412. }
  413. return
  414. }
  415. if len(batchResults) > 0 {
  416. resultsChan <- batchResults
  417. }
  418. }(batch)
  419. }
  420. }()
  421. // 等待结果
  422. select {
  423. case err := <-errChan:
  424. return nil, err
  425. case <-doneChan:
  426. return neckActiveHabitList, nil
  427. }
  428. }
  429. func (s *StoreEntry) CowGrowthCurve(ctx context.Context, req *pasturePb.CowGrowthCurveRequest) (*pasturePb.CowGrowthCurveResponse, error) {
  430. userModel, err := s.GetUserModel(ctx)
  431. if err != nil {
  432. return nil, xerr.WithStack(err)
  433. }
  434. cowInfo, err := s.GetCowInfoByEarNumber(ctx, userModel.AppPasture.Id, req.EarNumber)
  435. if err != nil {
  436. return nil, xerr.Customf("错误的牛只信息: %s", req.EarNumber)
  437. }
  438. weightList := make([]*model.EventWeight, 0)
  439. if err = s.DB.Table(new(model.EventWeight).TableName()).
  440. Where("cow_id = ?", cowInfo.Id).
  441. Where("pasture_id = ?", userModel.AppPasture.Id).
  442. Order("weight_at").
  443. Find(&weightList).Error; err != nil {
  444. return nil, xerr.WithStack(err)
  445. }
  446. eventCowLogList := make([]*model.EventCowLog, 0)
  447. eventCowLog := &model.EventCowLog{CowId: cowInfo.Id}
  448. if err = s.DB.Table(eventCowLog.TableName()).
  449. Where("pasture_id = ?", userModel.AppPasture.Id).
  450. Where("cow_id = ?", cowInfo.Id).
  451. Order("id desc").
  452. Find(&eventCowLogList).Error; err != nil {
  453. return nil, xerr.WithStack(err)
  454. }
  455. return &pasturePb.CowGrowthCurveResponse{
  456. Code: http.StatusOK,
  457. Msg: "ok",
  458. Data: model.EventWeightSlice(weightList).ToPB(cowInfo, eventCowLogList),
  459. }, nil
  460. }
  461. func (s *StoreEntry) CowLactCurve(ctx context.Context, req *pasturePb.CowLactCurveRequest) (*pasturePb.CowLactCurveResponse, error) {
  462. userModel, err := s.GetUserModel(ctx)
  463. if err != nil {
  464. return nil, xerr.WithStack(err)
  465. }
  466. cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(req.CowId))
  467. if err != nil {
  468. return nil, xerr.Customf("错误的牛只信息: %d", req.CowId)
  469. }
  470. cowLactList := make([]*model.CowLact, 0)
  471. if err = s.DB.Table(new(model.CowLact).TableName()).
  472. Where("cow_id = ?", req.CowId).
  473. Where("pasture_id = ?", userModel.AppPasture.Id).
  474. Order("lact").
  475. Find(&cowLactList).Error; err != nil {
  476. return nil, xerr.WithStack(err)
  477. }
  478. data := &pasturePb.CowLactCurveData{
  479. DateTime: make(map[int32]string),
  480. WeekAvgMilk: make([]float32, 0),
  481. DayMilk: make([]float32, 0),
  482. DHI: make([]float32, 0),
  483. MilkProductionTrend: make([]float32, 0),
  484. DayHigh: make([]int32, 0),
  485. DayRumina: make([]int32, 0),
  486. DayIntake: make([]int32, 0),
  487. DayInactive: make([]int32, 0),
  488. DayChew: make([]int32, 0),
  489. DayImmobility: make([]int32, 0),
  490. EstrusWarning: make(map[int32]int32),
  491. }
  492. if len(cowLactList) <= 0 {
  493. return &pasturePb.CowLactCurveResponse{
  494. Code: http.StatusOK,
  495. Msg: "ok",
  496. Data: data,
  497. }, nil
  498. }
  499. starTime := ""
  500. endTime := ""
  501. cowLactMap := make(map[int32]*model.CowLact)
  502. for _, v := range cowLactList {
  503. cowLactMap[v.Lact] = v
  504. if v.Lact == req.Lact {
  505. starTime = v.StartTime
  506. }
  507. }
  508. if st, ok := cowLactMap[req.Lact+1]; ok {
  509. et, _ := util.TimeParseLocal(model.LayoutDate2, st.StartTime)
  510. endTime = et.AddDate(0, 0, -1).Format(model.LayoutDate2)
  511. } else {
  512. endTime = time.Now().Local().Format(model.LayoutDate2)
  513. }
  514. neckRingList := make([]*model.NeckActiveHabit, 0)
  515. if err = s.DB.Model(new(model.NeckActiveHabit)).
  516. Where("neck_ring_number = ?", cowInfo.NeckRingNumber).
  517. Where("pasture_id = ?", userModel.AppPasture.Id).
  518. Where("cow_id > ?", 0).
  519. Where("heat_date BETWEEN ? AND ?", starTime, endTime).
  520. Order("heat_date, frameid").Find(&neckRingList).Error; err != nil {
  521. return nil, xerr.WithStack(err)
  522. }
  523. return &pasturePb.CowLactCurveResponse{
  524. Code: http.StatusOK,
  525. Msg: "ok",
  526. Data: data,
  527. }, nil
  528. }
  529. func (s *StoreEntry) BehaviorRate(ctx context.Context, req *pasturePb.CowBehaviorRateRequest) (*pasturePb.CowBehaviorRateResponse, error) {
  530. userModel, err := s.GetUserModel(ctx)
  531. if err != nil {
  532. return nil, xerr.WithStack(err)
  533. }
  534. cowInfo, err := s.GetCowInfoByEarNumber(ctx, userModel.AppPasture.Id, req.EarNumber)
  535. if err != nil {
  536. return nil, xerr.Customf("错误的牛只信息: %s", req.EarNumber)
  537. }
  538. if req.EndAt <= 0 || req.StartAt <= 0 || req.EndAt < req.StartAt {
  539. return nil, xerr.Customf("时间范围错误")
  540. }
  541. t1 := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
  542. t2 := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
  543. dataBetween, err := util.GetDaysBetween(t1, t2)
  544. if err != nil {
  545. return nil, xerr.WithStack(err)
  546. }
  547. neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  548. if err = s.DB.Model(new(model.NeckActiveHabit)).
  549. Select("heat_date,SUM(rumina) as rumina,SUM(intake) as intake,SUM(inactive) as inactive,SUM(gasp) AS gasp").
  550. Where("neck_ring_number = ?", cowInfo.NeckRingNumber).
  551. Where("pasture_id = ?", userModel.AppPasture.Id).
  552. Where("cow_id > ?", 0).
  553. Where("heat_date BETWEEN ? AND ?", t1, t2).
  554. Order("heat_date").
  555. Group("heat_date").
  556. Find(&neckActiveHabitList).Error; err != nil {
  557. }
  558. cowList := make([]*model.Cow, 0)
  559. if err = s.DB.Model(new(model.Cow)).
  560. Where("neck_ring_number != ?", "").
  561. Where("pasture_id = ?", userModel.AppPasture.Id).
  562. Where("pen_id = ?", cowInfo.PenId).
  563. Find(&cowList).Error; err != nil {
  564. }
  565. neckRingNumbers := make([]string, 0)
  566. for _, v := range cowList {
  567. neckRingNumbers = append(neckRingNumbers, v.NeckRingNumber)
  568. }
  569. groupNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  570. if err = s.DB.Model(new(model.NeckActiveHabit)).
  571. Select("heat_date,SUM(rumina) as rumina,SUM(intake) as intake,SUM(inactive) as inactive,SUM(gasp) AS gasp").
  572. Where("neck_ring_number IN ?", neckRingNumbers).
  573. Where("pasture_id = ?", userModel.AppPasture.Id).
  574. Where("cow_id > ?", 0).
  575. Where("heat_date BETWEEN ? AND ?", t1, t2).
  576. Order("heat_date").
  577. Group("heat_date").
  578. Find(&groupNeckActiveHabitList).Error; err != nil {
  579. }
  580. return &pasturePb.CowBehaviorRateResponse{
  581. Code: http.StatusOK,
  582. Msg: "ok",
  583. Data: model.NeckActiveHabitSlice(neckActiveHabitList).ToPB2(dataBetween, groupNeckActiveHabitList),
  584. }, nil
  585. }