analysis_more.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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. UnBreedIQR: &pasturePb.IQR{
  125. MaxY: 0,
  126. MinY: 0,
  127. MaxX: 0,
  128. MinX: 0,
  129. },
  130. BreedIQR: &pasturePb.IQR{
  131. MaxY: 0,
  132. MinY: 0,
  133. MaxX: 0,
  134. MinX: 0,
  135. },
  136. PregnantIQR: &pasturePb.IQR{
  137. MaxY: 0,
  138. MinY: 0,
  139. MaxX: 0,
  140. MinX: 0,
  141. },
  142. EmptyIQR: &pasturePb.IQR{
  143. MaxY: 0,
  144. MinY: 0,
  145. MaxX: 0,
  146. MinX: 0,
  147. },
  148. }
  149. breedStatus := s.BreedStatusEnumList()
  150. for _, v := range breedStatus {
  151. if v.Value == int32(pasturePb.BreedStatus_Abort) ||
  152. v.Value == int32(pasturePb.BreedStatus_Calving) ||
  153. v.Value == int32(pasturePb.BreedStatus_No_Mating) ||
  154. v.Value == int32(pasturePb.BreedStatus_Invalid) {
  155. continue
  156. }
  157. data.Headers = append(data.Headers, v.Label)
  158. switch v.Label {
  159. case "未配":
  160. data.Color = append(data.Color, "#b53827")
  161. case "空怀":
  162. data.Color = append(data.Color, "#2784b5")
  163. case "怀孕":
  164. data.Color = append(data.Color, "#2757b5")
  165. case "配种":
  166. data.Color = append(data.Color, "#27b560")
  167. }
  168. }
  169. if len(milkDailList) <= 0 {
  170. return &pasturePb.CowBehaviorDistributionResponse{
  171. Code: http.StatusOK,
  172. Msg: "ok",
  173. Data: data,
  174. }, nil
  175. }
  176. for _, v := range milkDailList {
  177. dayData := int32(0)
  178. switch req.BehaviorKind {
  179. case pasturePb.Behavior_Rumina:
  180. dayData = v.DayRumina
  181. case pasturePb.Behavior_Intake:
  182. dayData = v.DayIntake
  183. case pasturePb.Behavior_Reset:
  184. dayData = v.DayInactive
  185. case pasturePb.Behavior_Immobility:
  186. dayData = 24*60 - v.DayActive
  187. case pasturePb.Behavior_Chew:
  188. dayData = v.DayRumina + v.DayIntake
  189. }
  190. switch v.BreedStatus {
  191. case pasturePb.BreedStatus_Calving:
  192. data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
  193. EarNumber: v.EarNumber,
  194. CalvingAge: v.LactationAge,
  195. DayData: dayData,
  196. })
  197. case pasturePb.BreedStatus_Empty:
  198. data.Empty = append(data.Empty, &pasturePb.CowBehaviorData{
  199. EarNumber: v.EarNumber,
  200. CalvingAge: v.LactationAge,
  201. DayData: dayData,
  202. })
  203. case pasturePb.BreedStatus_UnBreed:
  204. data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
  205. EarNumber: v.EarNumber,
  206. CalvingAge: v.LactationAge,
  207. DayData: dayData,
  208. })
  209. case pasturePb.BreedStatus_Breeding:
  210. data.Breed = append(data.Breed, &pasturePb.CowBehaviorData{
  211. EarNumber: v.EarNumber,
  212. CalvingAge: v.LactationAge,
  213. DayData: dayData,
  214. })
  215. case pasturePb.BreedStatus_Pregnant:
  216. data.Pregnant = append(data.Pregnant, &pasturePb.CowBehaviorData{
  217. EarNumber: v.EarNumber,
  218. CalvingAge: v.LactationAge,
  219. DayData: dayData,
  220. })
  221. }
  222. }
  223. // 获取Breed的中位数
  224. if len(data.Breed) > 0 {
  225. data.MedianLine["breed"] = float32(getMedian(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  226. return int(p.DayData)
  227. }))
  228. data.BreedIQR = &pasturePb.IQR{
  229. MaxY: getIQR3(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  230. return int(p.DayData)
  231. }),
  232. MinY: getIQR1(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  233. return int(p.DayData)
  234. }),
  235. MaxX: getIQR3(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  236. return int(p.CalvingAge)
  237. }),
  238. MinX: getIQR1(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  239. return int(p.CalvingAge)
  240. }),
  241. }
  242. }
  243. if len(data.Pregnant) > 0 {
  244. data.MedianLine["pregnant"] = float32(getMedian(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  245. return int(p.DayData)
  246. }))
  247. data.PregnantIQR = &pasturePb.IQR{
  248. MaxY: getIQR3(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  249. return int(p.DayData)
  250. }),
  251. MinY: getIQR1(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  252. return int(p.DayData)
  253. }),
  254. MaxX: getIQR3(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  255. return int(p.CalvingAge)
  256. }),
  257. MinX: getIQR1(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  258. return int(p.CalvingAge)
  259. }),
  260. }
  261. }
  262. if len(data.Empty) > 0 {
  263. data.MedianLine["empty"] = float32(getMedian(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  264. return int(p.DayData)
  265. }))
  266. data.EmptyIQR = &pasturePb.IQR{
  267. MaxY: getIQR3(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  268. return int(p.DayData)
  269. }),
  270. MinY: getIQR1(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  271. return int(p.DayData)
  272. }),
  273. MaxX: getIQR3(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  274. return int(p.CalvingAge)
  275. }),
  276. MinX: getIQR1(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  277. return int(p.CalvingAge)
  278. }),
  279. }
  280. }
  281. if len(data.UnBreed) > 0 {
  282. data.MedianLine["unBreed"] = float32(getMedian(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  283. return int(p.DayData)
  284. }))
  285. data.UnBreedIQR = &pasturePb.IQR{
  286. MaxY: getIQR3(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  287. return int(p.DayData)
  288. }),
  289. MinY: getIQR1(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  290. return int(p.DayData)
  291. }),
  292. MaxX: getIQR3(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  293. return int(p.CalvingAge)
  294. }),
  295. MinX: getIQR1(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  296. return int(p.CalvingAge)
  297. }),
  298. }
  299. }
  300. return &pasturePb.CowBehaviorDistributionResponse{
  301. Code: http.StatusOK,
  302. Msg: "ok",
  303. Data: data,
  304. }, err
  305. }
  306. // 获取结构体切片中某个int字段的中位值
  307. func getMedian(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) float64 {
  308. // 1. 提取字段值
  309. values := make([]int, len(dataList))
  310. for i, p := range dataList {
  311. values[i] = getField(p)
  312. }
  313. // 2. 排序
  314. sort.Ints(values)
  315. // 3. 计算中位数
  316. n := len(values)
  317. if n == 0 {
  318. return 0
  319. }
  320. if n%2 == 1 {
  321. return float64(values[n/2])
  322. }
  323. return float64(values[n/2-1]+values[n/2]) / 2.0
  324. }
  325. func getIQR3(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) int32 {
  326. values := make([]int, len(dataList))
  327. for i, p := range dataList {
  328. values[i] = getField(p)
  329. }
  330. sort.Ints(values)
  331. n := len(values)
  332. if n < 3 {
  333. return 0
  334. }
  335. index := (n + 1) * 3 / 4
  336. if index >= n {
  337. return 0
  338. }
  339. return int32(values[index])
  340. }
  341. func getIQR1(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) int32 {
  342. values := make([]int, len(dataList))
  343. for i, p := range dataList {
  344. values[i] = getField(p)
  345. }
  346. sort.Ints(values)
  347. n := len(values)
  348. if n < 3 {
  349. return 0
  350. }
  351. index := (n + 1) / 4
  352. if index >= n {
  353. return 0
  354. }
  355. return int32(values[index])
  356. }