analysis_more.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  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. "github.com/nicksnyder/go-i18n/v2/i18n"
  11. "gitee.com/xuyiping_admin/pkg/xerr"
  12. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  13. )
  14. func (s *StoreEntry) PenBehavior(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*pasturePb.BarnBehaviorCurveResponse, error) {
  15. userModel, err := s.GetUserModel(ctx)
  16. if err != nil {
  17. return nil, err
  18. }
  19. if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt {
  20. messageId, _ := userModel.LanguageContent.Localize(&i18n.LocalizeConfig{
  21. MessageID: "auth.errorDateRange",
  22. })
  23. return nil, xerr.Customf(messageId)
  24. }
  25. startTime := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
  26. endTime := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
  27. penBehaviorList := make([]*model.PenBehavior, 0)
  28. if err = s.DB.Model(new(model.PenBehavior)).
  29. Where("pasture_id = ?", userModel.AppPasture.Id).
  30. Where("pen_id = ?", req.PenId).
  31. Where("heat_date BETWEEN ? AND ?", startTime, endTime).
  32. Find(&penBehaviorList).Error; err != nil {
  33. return nil, err
  34. }
  35. return &pasturePb.BarnBehaviorCurveResponse{
  36. Code: http.StatusOK,
  37. Msg: "ok",
  38. Data: model.PenBehaviorSlice(penBehaviorList).ToPB(),
  39. }, nil
  40. }
  41. func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*model.BarnMonitorResponse, error) {
  42. userModel, err := s.GetUserModel(ctx)
  43. if err != nil {
  44. return nil, xerr.WithStack(err)
  45. }
  46. if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt {
  47. messageId, _ := userModel.LanguageContent.Localize(&i18n.LocalizeConfig{
  48. MessageID: "auth.errorDateRange",
  49. })
  50. return nil, xerr.Customf(messageId)
  51. }
  52. startDate := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
  53. endDate := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
  54. dataTimeRange, err := util.GetDaysBetween(startDate, endDate)
  55. if err != nil {
  56. return nil, xerr.WithStack(err)
  57. }
  58. headers := make([]string, 0)
  59. if req.PenId <= 0 && req.BehaviorKind > 0 {
  60. penList, _ := s.GetPenList(ctx, userModel.AppPasture.Id)
  61. for _, v := range penList {
  62. headers = append(headers, v.Name)
  63. }
  64. } else {
  65. behaviorEnumList := s.Behavior(userModel, "")
  66. for _, v := range behaviorEnumList {
  67. headers = append(headers, v.Label)
  68. }
  69. headers = append(headers, "方差")
  70. }
  71. penBehaviorDayModelList := make([]*model.PenBehaviorDayModel, 0)
  72. pref := s.DB.Model(new(model.PenBehaviorDay)).
  73. Select(`distinct(heat_date) as heat_date,pen_id,pen_name,rumina_std,cow_count,day_rumina,day_intake,
  74. day_inactive,day_milk,day_rumina+day_intake as day_chew,24*60 - day_active as day_immobility`).
  75. Where("pasture_id = ?", userModel.AppPasture.Id).
  76. Where("heat_date BETWEEN ? AND ?", startDate, endDate)
  77. if req.PenId <= 0 && req.BehaviorKind > 0 {
  78. if err = pref.Group("heat_date,pen_id").
  79. Order("heat_date,pen_id").
  80. Find(&penBehaviorDayModelList).Error; err != nil {
  81. return nil, xerr.WithStack(err)
  82. }
  83. } else {
  84. if err = pref.Where("pen_id = ?", req.PenId).
  85. Order("heat_date").
  86. Find(&penBehaviorDayModelList).Error; err != nil {
  87. return nil, xerr.WithStack(err)
  88. }
  89. }
  90. return &model.BarnMonitorResponse{
  91. Code: http.StatusOK,
  92. Msg: "ok",
  93. Data: model.PenBehaviorDayModelSlice(penBehaviorDayModelList).ToPB(dataTimeRange, headers, req.BehaviorKind),
  94. }, err
  95. }
  96. func (s *StoreEntry) CowBehaviorDistribution(ctx context.Context, req *pasturePb.CowBehaviorDistributionRequest) (*pasturePb.CowBehaviorDistributionResponse, error) {
  97. userModel, err := s.GetUserModel(ctx)
  98. if err != nil {
  99. return nil, xerr.WithStack(err)
  100. }
  101. // 校验时间必须比当天时间小一天
  102. if time.Now().Local().Format(model.LayoutDate2) == req.DateTime {
  103. messageId, _ := userModel.LanguageContent.Localize(&i18n.LocalizeConfig{
  104. MessageID: "auth.errorDateRange",
  105. })
  106. return nil, xerr.Customf(messageId)
  107. }
  108. milkDailList := make([]*model.MilkDaily, 0)
  109. pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.Cow).TableName())).
  110. Joins(fmt.Sprintf("JOIN %s AS b on a.id = b.cow_id", new(model.MilkDaily).TableName())).
  111. Select("b.*").
  112. Where("a.pasture_id = ?", userModel.AppPasture.Id).
  113. Where("a.neck_ring_number != ?", "").
  114. Where("b.heat_date = ? ", req.DateTime).
  115. Where("b.day_high > ?", 0)
  116. if len(req.PenIds) > 0 {
  117. pref.Where("a.pen_id IN (?)", req.PenIds)
  118. }
  119. if err = pref.Order("b.breed_status,b.lactation_age").
  120. Find(&milkDailList).Error; err != nil {
  121. return nil, xerr.WithStack(err)
  122. }
  123. // 未配 空怀 怀孕 配种
  124. data := &pasturePb.CowBehaviorDistributionItem{
  125. Headers: make([]string, 0),
  126. Color: make([]string, 0),
  127. MedianLine: make(map[string]float32),
  128. CalvingAge: make([]int32, 0),
  129. UnBreed: make([]*pasturePb.CowBehaviorData, 0),
  130. Breed: make([]*pasturePb.CowBehaviorData, 0),
  131. Pregnant: make([]*pasturePb.CowBehaviorData, 0),
  132. Empty: make([]*pasturePb.CowBehaviorData, 0),
  133. UnBreedIQR: &pasturePb.IQR{
  134. MaxY: 0,
  135. MinY: 0,
  136. MaxX: 0,
  137. MinX: 0,
  138. },
  139. BreedIQR: &pasturePb.IQR{
  140. MaxY: 0,
  141. MinY: 0,
  142. MaxX: 0,
  143. MinX: 0,
  144. },
  145. PregnantIQR: &pasturePb.IQR{
  146. MaxY: 0,
  147. MinY: 0,
  148. MaxX: 0,
  149. MinX: 0,
  150. },
  151. EmptyIQR: &pasturePb.IQR{
  152. MaxY: 0,
  153. MinY: 0,
  154. MaxX: 0,
  155. MinX: 0,
  156. },
  157. }
  158. breedStatus := s.BreedStatusEnumList(userModel, "")
  159. for _, v := range breedStatus {
  160. if v.Value == int32(pasturePb.BreedStatus_Abort) ||
  161. v.Value == int32(pasturePb.BreedStatus_Calving) ||
  162. v.Value == int32(pasturePb.BreedStatus_No_Mating) ||
  163. v.Value == int32(pasturePb.BreedStatus_Invalid) {
  164. continue
  165. }
  166. data.Headers = append(data.Headers, v.Label)
  167. switch v.Label {
  168. case "未配":
  169. data.Color = append(data.Color, "#b53827")
  170. case "空怀":
  171. data.Color = append(data.Color, "#2784b5")
  172. case "怀孕":
  173. data.Color = append(data.Color, "#2757b5")
  174. case "配种":
  175. data.Color = append(data.Color, "#27b560")
  176. }
  177. }
  178. if len(milkDailList) <= 0 {
  179. return &pasturePb.CowBehaviorDistributionResponse{
  180. Code: http.StatusOK,
  181. Msg: "ok",
  182. Data: data,
  183. }, nil
  184. }
  185. for _, v := range milkDailList {
  186. dayData := int32(0)
  187. switch req.BehaviorKind {
  188. case pasturePb.Behavior_Rumina:
  189. dayData = v.DayRumina
  190. case pasturePb.Behavior_Intake:
  191. dayData = v.DayIntake
  192. case pasturePb.Behavior_Reset:
  193. dayData = v.DayInactive
  194. case pasturePb.Behavior_Immobility:
  195. dayData = 24*60 - v.DayActive
  196. case pasturePb.Behavior_Chew:
  197. dayData = v.DayRumina + v.DayIntake
  198. }
  199. switch v.BreedStatus {
  200. case pasturePb.BreedStatus_Calving:
  201. data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
  202. EarNumber: v.EarNumber,
  203. CalvingAge: v.LactationAge,
  204. DayData: dayData,
  205. })
  206. case pasturePb.BreedStatus_Empty:
  207. data.Empty = append(data.Empty, &pasturePb.CowBehaviorData{
  208. EarNumber: v.EarNumber,
  209. CalvingAge: v.LactationAge,
  210. DayData: dayData,
  211. })
  212. case pasturePb.BreedStatus_UnBreed:
  213. data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
  214. EarNumber: v.EarNumber,
  215. CalvingAge: v.LactationAge,
  216. DayData: dayData,
  217. })
  218. case pasturePb.BreedStatus_Breeding:
  219. data.Breed = append(data.Breed, &pasturePb.CowBehaviorData{
  220. EarNumber: v.EarNumber,
  221. CalvingAge: v.LactationAge,
  222. DayData: dayData,
  223. })
  224. case pasturePb.BreedStatus_Pregnant:
  225. data.Pregnant = append(data.Pregnant, &pasturePb.CowBehaviorData{
  226. EarNumber: v.EarNumber,
  227. CalvingAge: v.LactationAge,
  228. DayData: dayData,
  229. })
  230. }
  231. }
  232. // 获取Breed的中位数
  233. if len(data.Breed) > 0 {
  234. data.MedianLine["breed"] = float32(getMedian(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  235. return int(p.DayData)
  236. }))
  237. data.BreedIQR = &pasturePb.IQR{
  238. MaxY: getIQR3(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  239. return int(p.DayData)
  240. }),
  241. MinY: getIQR1(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  242. return int(p.DayData)
  243. }),
  244. MaxX: getIQR3(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  245. return int(p.CalvingAge)
  246. }),
  247. MinX: getIQR1(data.Breed, func(p *pasturePb.CowBehaviorData) int {
  248. return int(p.CalvingAge)
  249. }),
  250. }
  251. }
  252. if len(data.Pregnant) > 0 {
  253. data.MedianLine["pregnant"] = float32(getMedian(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  254. return int(p.DayData)
  255. }))
  256. data.PregnantIQR = &pasturePb.IQR{
  257. MaxY: getIQR3(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  258. return int(p.DayData)
  259. }),
  260. MinY: getIQR1(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  261. return int(p.DayData)
  262. }),
  263. MaxX: getIQR3(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  264. return int(p.CalvingAge)
  265. }),
  266. MinX: getIQR1(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
  267. return int(p.CalvingAge)
  268. }),
  269. }
  270. }
  271. if len(data.Empty) > 0 {
  272. data.MedianLine["empty"] = float32(getMedian(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  273. return int(p.DayData)
  274. }))
  275. data.EmptyIQR = &pasturePb.IQR{
  276. MaxY: getIQR3(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  277. return int(p.DayData)
  278. }),
  279. MinY: getIQR1(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  280. return int(p.DayData)
  281. }),
  282. MaxX: getIQR3(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  283. return int(p.CalvingAge)
  284. }),
  285. MinX: getIQR1(data.Empty, func(p *pasturePb.CowBehaviorData) int {
  286. return int(p.CalvingAge)
  287. }),
  288. }
  289. }
  290. if len(data.UnBreed) > 0 {
  291. data.MedianLine["unBreed"] = float32(getMedian(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  292. return int(p.DayData)
  293. }))
  294. data.UnBreedIQR = &pasturePb.IQR{
  295. MaxY: getIQR3(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  296. return int(p.DayData)
  297. }),
  298. MinY: getIQR1(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  299. return int(p.DayData)
  300. }),
  301. MaxX: getIQR3(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  302. return int(p.CalvingAge)
  303. }),
  304. MinX: getIQR1(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
  305. return int(p.CalvingAge)
  306. }),
  307. }
  308. }
  309. return &pasturePb.CowBehaviorDistributionResponse{
  310. Code: http.StatusOK,
  311. Msg: "ok",
  312. Data: data,
  313. }, err
  314. }
  315. // 获取结构体切片中某个int字段的中位值
  316. func getMedian(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) float64 {
  317. // 1. 提取字段值
  318. values := make([]int, len(dataList))
  319. for i, p := range dataList {
  320. values[i] = getField(p)
  321. }
  322. // 2. 排序
  323. sort.Ints(values)
  324. // 3. 计算中位数
  325. n := len(values)
  326. if n == 0 {
  327. return 0
  328. }
  329. if n%2 == 1 {
  330. return float64(values[n/2])
  331. }
  332. return float64(values[n/2-1]+values[n/2]) / 2.0
  333. }
  334. func getIQR3(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) int32 {
  335. values := make([]int, len(dataList))
  336. for i, p := range dataList {
  337. values[i] = getField(p)
  338. }
  339. sort.Ints(values)
  340. n := len(values)
  341. if n < 3 {
  342. return 0
  343. }
  344. index := (n + 1) * 3 / 4
  345. if index >= n {
  346. return 0
  347. }
  348. return int32(values[index])
  349. }
  350. func getIQR1(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) int32 {
  351. values := make([]int, len(dataList))
  352. for i, p := range dataList {
  353. values[i] = getField(p)
  354. }
  355. sort.Ints(values)
  356. n := len(values)
  357. if n < 3 {
  358. return 0
  359. }
  360. index := (n + 1) / 4
  361. if index >= n {
  362. return 0
  363. }
  364. return int32(values[index])
  365. }