cow.go 21 KB

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