analysis_more.go 11 KB

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