analysis_more.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package backend
  2. import (
  3. "context"
  4. "fmt"
  5. "kpt-pasture/model"
  6. "kpt-pasture/util"
  7. "net/http"
  8. "sort"
  9. "time"
  10. "gitee.com/xuyiping_admin/pkg/xerr"
  11. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  12. )
  13. func (s *StoreEntry) PenBehavior(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*pasturePb.BarnBehaviorCurveResponse, error) {
  14. userModel, err := s.GetUserModel(ctx)
  15. if err != nil {
  16. return nil, err
  17. }
  18. if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt {
  19. return nil, xerr.Customf("时间范围错误")
  20. }
  21. startTime := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
  22. endTime := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
  23. penBehaviorList := make([]*model.PenBehavior, 0)
  24. if err = s.DB.Model(new(model.PenBehavior)).
  25. Where("pasture_id = ?", userModel.AppPasture.Id).
  26. Where("pen_id = ?", req.PenId).
  27. Where("heat_date BETWEEN ? AND ?", startTime, endTime).
  28. Find(&penBehaviorList).Error; err != nil {
  29. return nil, err
  30. }
  31. return &pasturePb.BarnBehaviorCurveResponse{
  32. Code: http.StatusOK,
  33. Msg: "ok",
  34. Data: model.PenBehaviorSlice(penBehaviorList).ToPB(),
  35. }, nil
  36. }
  37. func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*model.BarnMonitorResponse, error) {
  38. userModel, err := s.GetUserModel(ctx)
  39. if err != nil {
  40. return nil, xerr.WithStack(err)
  41. }
  42. if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt {
  43. return nil, xerr.Customf("时间范围错误")
  44. }
  45. startDate := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
  46. endDate := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
  47. dataTimeRange, err := util.GetDaysBetween(startDate, endDate)
  48. if err != nil {
  49. return nil, xerr.WithStack(err)
  50. }
  51. headers := make([]string, 0)
  52. if req.PenId <= 0 && req.BehaviorKind > 0 {
  53. penList, _ := s.GetPenList(ctx, userModel.AppPasture.Id)
  54. for _, v := range penList {
  55. headers = append(headers, v.Name)
  56. }
  57. } else {
  58. behaviorEnumList := s.Behavior("")
  59. for _, v := range behaviorEnumList {
  60. headers = append(headers, v.Label)
  61. }
  62. headers = append(headers, "方差")
  63. }
  64. penBehaviorDayModelList := make([]*model.PenBehaviorDayModel, 0)
  65. pref := s.DB.Model(new(model.PenBehaviorDay)).
  66. Select(`distinct(heat_date) as heat_date,pen_id,pen_name,rumina_std,cow_count,day_rumina,day_intake,
  67. day_inactive,day_milk,day_rumina+day_intake as day_chew,24*60 - day_active as day_immobility`).
  68. Where("pasture_id = ?", userModel.AppPasture.Id).
  69. Where("heat_date BETWEEN ? AND ?", startDate, endDate)
  70. if req.PenId <= 0 && req.BehaviorKind > 0 {
  71. if err = pref.Group("heat_date,pen_id").
  72. Order("heat_date,pen_id").
  73. Find(&penBehaviorDayModelList).Error; err != nil {
  74. return nil, xerr.WithStack(err)
  75. }
  76. } else {
  77. if err = pref.Where("pen_id = ?", req.PenId).
  78. Order("heat_date").
  79. Find(&penBehaviorDayModelList).Error; err != nil {
  80. return nil, xerr.WithStack(err)
  81. }
  82. }
  83. return &model.BarnMonitorResponse{
  84. Code: http.StatusOK,
  85. Msg: "ok",
  86. Data: model.PenBehaviorDayModelSlice(penBehaviorDayModelList).ToPB(dataTimeRange, headers, req.BehaviorKind),
  87. }, err
  88. }
  89. func (s *StoreEntry) CowBehaviorDistribution(ctx context.Context, req *pasturePb.CowBehaviorDistributionRequest) (*pasturePb.CowBehaviorDistributionResponse, error) {
  90. userModel, err := s.GetUserModel(ctx)
  91. if err != nil {
  92. return nil, xerr.WithStack(err)
  93. }
  94. // 校验时间必须比当天时间小一天
  95. if time.Now().Local().Format(model.LayoutDate2) == req.DateTime {
  96. return nil, xerr.Customf("时间范围错误")
  97. }
  98. milkDailList := make([]*model.MilkDaily, 0)
  99. pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.Cow).TableName())).
  100. Joins(fmt.Sprintf("JOIN %s AS b on a.id = b.cow_id", new(model.MilkDaily).TableName())).
  101. Select("b.*").
  102. Where("a.pasture_id = ?", userModel.AppPasture.Id).
  103. Where("a.neck_ring_number != ?", "").
  104. Where("a.sex = ?", pasturePb.Genders_Female).
  105. Where("b.heat_date = ? ", req.DateTime).
  106. Where("b.day_high > ?", 0)
  107. if len(req.PenIds) > 0 {
  108. pref.Where("a.pen_id IN (?)", req.PenIds)
  109. }
  110. if err = pref.Order("b.breed_status,b.lactation_age").
  111. Find(&milkDailList).Error; err != nil {
  112. return nil, xerr.WithStack(err)
  113. }
  114. // 未配 空怀 怀孕 配种
  115. data := &pasturePb.CowBehaviorDistributionItem{
  116. Headers: make([]string, 0),
  117. Color: make([]string, 0),
  118. MedianLine: make(map[string]float32),
  119. CalvingAge: make([]int32, 0),
  120. UnBreed: make([]*pasturePb.CowBehaviorData, 0),
  121. Breed: make([]*pasturePb.CowBehaviorData, 0),
  122. Pregnant: make([]*pasturePb.CowBehaviorData, 0),
  123. Empty: make([]*pasturePb.CowBehaviorData, 0),
  124. }
  125. breedStatus := s.BreedStatusEnumList()
  126. for _, v := range breedStatus {
  127. if v.Value == int32(pasturePb.BreedStatus_Abort) ||
  128. v.Value == int32(pasturePb.BreedStatus_Calving) ||
  129. v.Value == int32(pasturePb.BreedStatus_No_Mating) ||
  130. v.Value == int32(pasturePb.BreedStatus_Invalid) {
  131. continue
  132. }
  133. data.Headers = append(data.Headers, v.Label)
  134. switch v.Label {
  135. case "未配":
  136. data.Color = append(data.Color, "#b53827")
  137. case "空怀":
  138. data.Color = append(data.Color, "#2784b5")
  139. case "怀孕":
  140. data.Color = append(data.Color, "#2757b5")
  141. case "配种":
  142. data.Color = append(data.Color, "#27b560")
  143. }
  144. }
  145. if len(milkDailList) <= 0 {
  146. return &pasturePb.CowBehaviorDistributionResponse{
  147. Code: http.StatusOK,
  148. Msg: "ok",
  149. Data: data,
  150. }, nil
  151. }
  152. for _, v := range milkDailList {
  153. dayData := int32(0)
  154. switch req.BehaviorKind {
  155. case pasturePb.Behavior_Rumina:
  156. dayData = v.DayRumina
  157. case pasturePb.Behavior_Intake:
  158. dayData = v.DayIntake
  159. case pasturePb.Behavior_Reset:
  160. dayData = v.DayInactive
  161. case pasturePb.Behavior_Immobility:
  162. dayData = 24*60 - v.DayActive
  163. case pasturePb.Behavior_Chew:
  164. dayData = v.DayRumina + v.DayIntake
  165. }
  166. switch v.BreedStatus {
  167. case pasturePb.BreedStatus_Calving:
  168. data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
  169. EarNumber: v.EarNumber,
  170. CalvingAge: v.LactationAge,
  171. DayData: dayData,
  172. })
  173. case pasturePb.BreedStatus_Empty:
  174. data.Empty = append(data.Empty, &pasturePb.CowBehaviorData{
  175. EarNumber: v.EarNumber,
  176. CalvingAge: v.LactationAge,
  177. DayData: dayData,
  178. })
  179. case pasturePb.BreedStatus_UnBreed:
  180. data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
  181. EarNumber: v.EarNumber,
  182. CalvingAge: v.LactationAge,
  183. DayData: dayData,
  184. })
  185. case pasturePb.BreedStatus_Breeding:
  186. data.Breed = append(data.Breed, &pasturePb.CowBehaviorData{
  187. EarNumber: v.EarNumber,
  188. CalvingAge: v.LactationAge,
  189. DayData: dayData,
  190. })
  191. case pasturePb.BreedStatus_Pregnant:
  192. data.Pregnant = append(data.Pregnant, &pasturePb.CowBehaviorData{
  193. EarNumber: v.EarNumber,
  194. CalvingAge: v.LactationAge,
  195. DayData: dayData,
  196. })
  197. }
  198. }
  199. // 获取Breed的中位数
  200. if len(data.Breed) > 0 {
  201. data.MedianLine["breed"] = float32(getMedian(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  202. return int(p.DayData)
  203. }))
  204. }
  205. if len(data.Pregnant) > 0 {
  206. data.MedianLine["pregnant"] = float32(getMedian(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  207. return int(p.DayData)
  208. }))
  209. }
  210. if len(data.Empty) > 0 {
  211. data.MedianLine["empty"] = float32(getMedian(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  212. return int(p.DayData)
  213. }))
  214. }
  215. if len(data.UnBreed) > 0 {
  216. data.MedianLine["unBreed"] = float32(getMedian(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  217. return int(p.DayData)
  218. }))
  219. }
  220. return &pasturePb.CowBehaviorDistributionResponse{
  221. Code: http.StatusOK,
  222. Msg: "ok",
  223. Data: data,
  224. }, err
  225. }
  226. // 获取结构体切片中某个int字段的中位值
  227. func getMedian(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) float64 {
  228. // 1. 提取字段值
  229. values := make([]int, len(dataList))
  230. for i, p := range dataList {
  231. values[i] = getField(p)
  232. }
  233. // 2. 排序
  234. sort.Ints(values)
  235. // 3. 计算中位数
  236. n := len(values)
  237. if n == 0 {
  238. return 0
  239. }
  240. if n%2 == 1 {
  241. return float64(values[n/2])
  242. }
  243. return float64(values[n/2-1]+values[n/2]) / 2.0
  244. }