analysis.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456
  1. package backend
  2. import (
  3. "context"
  4. "fmt"
  5. "kpt-pasture/model"
  6. "kpt-pasture/util"
  7. "net/http"
  8. "time"
  9. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  10. "gitee.com/xuyiping_admin/pkg/xerr"
  11. )
  12. // WeightScatterPlot 体重散点图 获取图表数据
  13. func (s *StoreEntry) WeightScatterPlot(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest, pagination *pasturePb.PaginationModel) (*pasturePb.GrowthCurvesResponse, error) {
  14. userModel, err := s.GetUserModel(ctx)
  15. if err != nil {
  16. return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
  17. }
  18. // 查询数据
  19. cowList := make([]*model.Cow, 0)
  20. pref := s.DB.Model(new(model.Cow)).
  21. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
  22. Where("pasture_id = ?", userModel.AppPasture.Id)
  23. if req.EarNumber != "" {
  24. pref.Where("ear_number = ?", req.EarNumber)
  25. }
  26. if len(req.PenIds) > 0 {
  27. pref.Where("pen_id IN (?)", req.PenIds)
  28. }
  29. if len(req.AdmissionDate) == 2 && len(req.AdmissionDate[0]) > 0 && len(req.AdmissionDate[1]) > 0 {
  30. t0, _ := util.TimeParseLocal(model.LayoutDate2, req.AdmissionDate[0])
  31. t1, _ := util.TimeParseLocal(model.LayoutDate2, req.AdmissionDate[1])
  32. pref.Where("admission_at BETWEEN ? AND ?", t0.Unix(), t1.Unix()+86399)
  33. }
  34. if err = pref.Find(&cowList).Error; err != nil {
  35. return nil, err
  36. }
  37. if err != nil {
  38. return nil, xerr.WithStack(err)
  39. }
  40. // 计算图表数据
  41. chartsList := &pasturePb.Charts{
  42. CowId: make([]int32, 0),
  43. Weight: make([]float32, 0),
  44. AdmissionAge: make([]int32, 0),
  45. }
  46. cowData := make([]*pasturePb.CowList, 0)
  47. for _, cow := range cowList {
  48. currentWeight := float32(cow.CurrentWeight) / 1000
  49. admissionAtFormat := ""
  50. if cow.AdmissionAt > 0 {
  51. admissionAtFormat = time.Unix(cow.AdmissionAt, 0).Local().Format(model.LayoutDate2)
  52. }
  53. cowData = append(cowData, &pasturePb.CowList{
  54. CowId: int32(cow.Id),
  55. EarNumber: cow.EarNumber,
  56. DayAge: cow.GetDayAge(),
  57. PenName: cow.PenName,
  58. CurrentWeight: currentWeight,
  59. BirthAt: int32(cow.BirthAt),
  60. BirthWeight: float32(cow.BirthWeight) / 1000,
  61. LastWeightAt: int32(cow.LastWeightAt),
  62. AverageDailyWeightGain: float32(cow.GetAverageDailyWeight()),
  63. PreviousStageDailyWeight: float32(cow.GetPreviousStageDailyWeight()),
  64. AdmissionAge: cow.GetAdmissionAge(),
  65. AdmissionAtFormat: admissionAtFormat,
  66. })
  67. chartsList.CowId = append(chartsList.CowId, int32(cow.Id))
  68. chartsList.Weight = append(chartsList.Weight, currentWeight)
  69. chartsList.AdmissionAge = append(chartsList.AdmissionAge, cow.GetAdmissionAge())
  70. }
  71. // 返回数据
  72. return &pasturePb.GrowthCurvesResponse{
  73. Code: http.StatusOK,
  74. Msg: "success",
  75. Data: &pasturePb.GrowthCurveData{
  76. Table: cowData,
  77. Charts: chartsList,
  78. },
  79. }, nil
  80. }
  81. func (s *StoreEntry) WeightRange(ctx context.Context, req *pasturePb.WeightRangeRequest, pagination *pasturePb.PaginationModel) (*pasturePb.WeightRangeResponse, error) {
  82. userModel, err := s.GetUserModel(ctx)
  83. if err != nil {
  84. return nil, xerr.WithStack(err)
  85. }
  86. cowWeightRange := make([]*model.CowWeightRange, 0)
  87. prefix := s.DB.Model(new(model.Cow)).
  88. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
  89. Where("pasture_id = ?", userModel.AppPasture.Id)
  90. if req.CowKind > 0 {
  91. prefix.Where("cow_kind = ?", req.CowKind)
  92. }
  93. if err = prefix.Select(`
  94. CASE
  95. WHEN current_weight BETWEEN 0 AND 50000 THEN '0-50'
  96. WHEN current_weight BETWEEN 50001 AND 100000 THEN '51-100'
  97. WHEN current_weight BETWEEN 100001 AND 150000 THEN '101-150'
  98. WHEN current_weight BETWEEN 150001 AND 200000 THEN '151-200'
  99. WHEN current_weight BETWEEN 200001 AND 250000 THEN '201-250'
  100. WHEN current_weight BETWEEN 250001 AND 300000 THEN '251-300'
  101. WHEN current_weight BETWEEN 300001 AND 350000 THEN '301-350'
  102. WHEN current_weight BETWEEN 350001 AND 400000 THEN '351-400'
  103. WHEN current_weight BETWEEN 400001 AND 450000 THEN '401-450'
  104. WHEN current_weight BETWEEN 450001 AND 500000 THEN '451-500'
  105. WHEN current_weight BETWEEN 500001 AND 550000 THEN '500-550'
  106. WHEN current_weight BETWEEN 550001 AND 600000 THEN '551-600'
  107. WHEN current_weight BETWEEN 600001 AND 650000 THEN '601-650'
  108. WHEN current_weight BETWEEN 650001 AND 700000 THEN '651-700'
  109. WHEN current_weight BETWEEN 700001 AND 750000 THEN '701-750'
  110. ELSE '750+'
  111. END AS weight_range,
  112. COUNT(*) AS count `,
  113. ).Group("weight_range").
  114. Order("MIN(current_weight)").
  115. Find(&cowWeightRange).Error; err != nil {
  116. return nil, err
  117. }
  118. if len(cowWeightRange) == 0 {
  119. return &pasturePb.WeightRangeResponse{
  120. Code: http.StatusOK,
  121. Msg: "ok",
  122. Data: &pasturePb.WeightRangeData{
  123. CowList: make([]*pasturePb.CowList, 0),
  124. WeightBarChart: &pasturePb.WeightBarChart{
  125. Header: make([]string, 0),
  126. Data: make([]int32, 0),
  127. },
  128. Total: 0,
  129. PageSize: pagination.PageSize,
  130. Page: pagination.Page,
  131. },
  132. }, nil
  133. }
  134. header := make([]string, 0)
  135. data := make([]int32, 0)
  136. for _, v := range cowWeightRange {
  137. header = append(header, v.WeightRange)
  138. data = append(data, v.Count)
  139. }
  140. // 牛只详情列表
  141. pref := s.DB.Model(new(model.Cow)).
  142. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
  143. Where("pasture_id = ?", userModel.AppPasture.Id)
  144. if req.CowKind > 0 {
  145. pref.Where("cow_kind = ?", req.CowKind)
  146. }
  147. cowList := make([]*model.Cow, 0)
  148. if req.MinWeight >= 0 && req.MaxWeight >= 0 && req.MinWeight < req.MaxWeight {
  149. pref.Where("current_weight BETWEEN ? AND ? ", req.MinWeight*1000, req.MaxWeight*1000)
  150. }
  151. var count int64
  152. if err = pref.Order("id desc").
  153. Count(&count).
  154. Limit(int(pagination.PageSize)).
  155. Offset(int(pagination.PageOffset)).
  156. Find(&cowList).Error; err != nil {
  157. return nil, err
  158. }
  159. penMap := s.PenMap(ctx, userModel.AppPasture.Id)
  160. return &pasturePb.WeightRangeResponse{
  161. Code: http.StatusOK,
  162. Msg: "ok",
  163. Data: &pasturePb.WeightRangeData{
  164. CowList: model.CowSlice(cowList).WeightRangeToPB(penMap),
  165. WeightBarChart: &pasturePb.WeightBarChart{
  166. Header: header,
  167. Data: data,
  168. },
  169. Total: int32(count),
  170. PageSize: pagination.PageSize,
  171. Page: pagination.Page,
  172. },
  173. }, nil
  174. }
  175. func (s *StoreEntry) MatingTimely(ctx context.Context, req *pasturePb.MatingTimelyRequest) (*model.MatingTimelyResponse, error) {
  176. userModel, err := s.GetUserModel(ctx)
  177. if err != nil {
  178. return nil, xerr.WithStack(err)
  179. }
  180. matingTimelyChart := make([]*model.MatingTimelyChart, 0)
  181. pastureWhereSql := fmt.Sprintf(" AND pasture_id = %d", userModel.AppPasture.Id)
  182. sql := `SELECT calving_age,cow_type, DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') AS reality_day, lact_group
  183. FROM (
  184. SELECT calving_age, cow_type,reality_day, '0' AS lact_group
  185. FROM event_mating
  186. WHERE lact = 0 AND status = 1 ` + pastureWhereSql + `
  187. UNION ALL
  188. SELECT calving_age,cow_type, reality_day, '1' AS lact_group
  189. FROM event_mating
  190. WHERE lact = 1 AND status = 1 ` + pastureWhereSql + `
  191. UNION ALL
  192. SELECT calving_age,cow_type, reality_day, '2' AS lact_group
  193. FROM event_mating
  194. WHERE lact = 2 AND status = 1 ` + pastureWhereSql + `
  195. UNION ALL
  196. SELECT calving_age, cow_type, reality_day, '3+' AS lact_group
  197. FROM event_mating
  198. WHERE lact >= 3 AND status = 1 ` + pastureWhereSql + `
  199. ) AS subquery WHERE 1 = 1 `
  200. whereSql := ""
  201. if req.CowType > 0 {
  202. whereSql += fmt.Sprintf("AND cow_type = %d ", req.CowType)
  203. }
  204. if req.StartDayAt > 0 && req.EndDayAt > 0 {
  205. whereSql += fmt.Sprintf("AND reality_day BETWEEN %d AND %d", req.StartDayAt, req.EndDayAt)
  206. }
  207. if err = s.DB.Raw(fmt.Sprintf("%s %s", sql, whereSql)).Find(&matingTimelyChart).Error; err != nil {
  208. return nil, err
  209. }
  210. chart := &model.CowMatingChart{
  211. Lact0: make([][]string, 0),
  212. Lact1: make([][]string, 0),
  213. Lact2: make([][]string, 0),
  214. Lact3: make([][]string, 0),
  215. }
  216. if len(matingTimelyChart) == 0 {
  217. return &model.MatingTimelyResponse{
  218. Code: http.StatusOK,
  219. Msg: "ok",
  220. Data: &model.MatingTimelyData{
  221. CowList: make([]*pasturePb.CowList, 0),
  222. Chart: chart,
  223. },
  224. }, nil
  225. }
  226. for _, v := range matingTimelyChart {
  227. t, _ := util.TimeParseLocal(model.LayoutDate2, v.RealityDay)
  228. switch v.LactGroup {
  229. case "0":
  230. chart.Lact0 = append(chart.Lact0, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  231. case "1":
  232. chart.Lact1 = append(chart.Lact1, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  233. case "2":
  234. chart.Lact2 = append(chart.Lact2, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  235. case "3+":
  236. chart.Lact3 = append(chart.Lact3, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  237. }
  238. }
  239. // 牛只详情列表
  240. eventMatingList := make([]*model.EventMating, 0)
  241. pref := s.DB.Model(new(model.EventMating)).
  242. Where("status = ?", pasturePb.IsShow_Ok)
  243. if req.CowType > 0 {
  244. pref.Where("cow_type = ?", req.CowType)
  245. }
  246. if req.StartDayAt > 0 && req.EndDayAt > 0 {
  247. pref.Where("reality_day BETWEEN ? AND ?", req.StartDayAt, req.EndDayAt)
  248. }
  249. if err = pref.Find(&eventMatingList).Error; err != nil {
  250. return nil, err
  251. }
  252. return &model.MatingTimelyResponse{
  253. Code: http.StatusOK,
  254. Msg: "ok",
  255. Data: &model.MatingTimelyData{
  256. CowList: model.EventMatingSlice(eventMatingList).ToPB2(),
  257. Chart: chart,
  258. },
  259. }, nil
  260. }
  261. func (s *StoreEntry) PenWeight(ctx context.Context, req *pasturePb.PenWeightRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PenWeightResponse, error) {
  262. userModel, err := s.GetUserModel(ctx)
  263. if err != nil {
  264. return nil, xerr.WithStack(err)
  265. }
  266. penWeightList := make([]*model.PenWeight, 0)
  267. pref := s.DB.Model(new(model.Cow)).
  268. Select(`
  269. pen_id,
  270. CEILING(AVG(current_weight) / 1000 ) AS avg_weight,
  271. CEILING(SUM(current_weight) / 1000 ) AS all_weight,
  272. COUNT(*) AS cow_count`,
  273. ).
  274. Where("pasture_id = ?", userModel.AppPasture.Id).
  275. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  276. if len(req.PenId) > 0 && req.BarId <= 0 {
  277. pref.Where("pen_id IN ?", req.PenId)
  278. }
  279. if err = pref.Group("pen_id").
  280. Order("pen_id").
  281. Find(&penWeightList).Error; err != nil {
  282. return nil, err
  283. }
  284. chart := &pasturePb.PenWeightChart{
  285. Header: make([]string, 0),
  286. AllWeight: make([]int32, 0),
  287. AvgWeight: make([]int32, 0),
  288. CowCount: make([]int32, 0),
  289. }
  290. if len(penWeightList) <= 0 {
  291. return &pasturePb.PenWeightResponse{
  292. Code: http.StatusOK,
  293. Msg: "ok",
  294. Data: &pasturePb.PenWeightData{
  295. CowList: make([]*pasturePb.CowList, 0),
  296. Chart: chart,
  297. },
  298. }, nil
  299. }
  300. cowList := make([]*model.Cow, 0)
  301. var count int64 = 0
  302. prefList := s.DB.Model(new(model.Cow)).
  303. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  304. if len(req.PenId) > 0 {
  305. prefList.Where("pen_id IN (?)", req.PenId)
  306. } else if req.BarId > 0 {
  307. prefList.Where("pen_id = ?", []int32{req.BarId})
  308. }
  309. if err = prefList.Count(&count).Limit(int(pagination.PageSize)).
  310. Offset(int(pagination.PageOffset)).Order("pen_id").
  311. Find(&cowList).Error; err != nil {
  312. return nil, xerr.WithStack(err)
  313. }
  314. penMap := s.PenMap(ctx, userModel.AppPasture.Id)
  315. return &pasturePb.PenWeightResponse{
  316. Code: http.StatusOK,
  317. Msg: "ok",
  318. Data: &pasturePb.PenWeightData{
  319. CowList: model.CowSlice(cowList).ToPB2(penWeightList),
  320. Total: int32(count),
  321. Page: pagination.Page,
  322. PageSize: pagination.PageSize,
  323. Chart: model.PenWeightSlice(penWeightList).ToPB(penMap),
  324. },
  325. }, nil
  326. }
  327. func (s *StoreEntry) AbortionRate(ctx context.Context, req *pasturePb.AbortionRateRequest) (*pasturePb.AbortionRateResponse, error) {
  328. userModel, err := s.GetUserModel(ctx)
  329. if err != nil {
  330. return nil, xerr.WithStack(err)
  331. }
  332. dayTimeList, err := util.GetMonthsInRange(req.StartDayTime, req.EndDayTime)
  333. if err != nil {
  334. return nil, xerr.WithStack(err)
  335. }
  336. // 2. 创建月份映射表用于快速查找
  337. monthMap := make(map[string]*pasturePb.AbortionRateTable)
  338. for _, month := range dayTimeList {
  339. monthMap[month] = &pasturePb.AbortionRateTable{
  340. MonthName: month,
  341. AbortionCount: 0,
  342. PregnantCount: 0,
  343. AbortionRate: 0,
  344. }
  345. }
  346. // 历史每月怀孕牛头数量
  347. cowPregnantMonthList := make([]*model.CowPregnantMonth, 0)
  348. pref := s.DB.Model(new(model.CowPregnant)).
  349. Select(`COUNT(cow_id) AS cow_count,date_time as month`).
  350. Where("cow_type = ?", req.CowType).
  351. Where("pasture_id = ?", userModel.AppPasture.Id)
  352. if req.CowType == pasturePb.CowType_Breeding_Calf && req.Lact >= 0 && req.Lact <= 3 {
  353. pref.Where("lact = ?", req.Lact)
  354. } else {
  355. pref.Where("lact > ?", req.Lact)
  356. }
  357. if err = pref.Group("month").
  358. Find(&cowPregnantMonthList).Error; err != nil {
  359. return nil, xerr.WithStack(err)
  360. }
  361. // 更新月份映射表中的怀孕数量
  362. for _, v := range cowPregnantMonthList {
  363. if table, exists := monthMap[v.Month]; exists {
  364. table.PregnantCount = v.CowCount
  365. }
  366. }
  367. // 历史每月流产牛头数量
  368. cowAbortionMonthList := make([]*model.CowPregnantMonth, 0)
  369. pref2 := s.DB.Model(new(model.EventAbortion)).
  370. Select(`COUNT(cow_id) AS cow_count,DATE_FORMAT(FROM_UNIXTIME(abortion_at),'%Y-%m') as month`).
  371. Where("cow_type = ?", req.CowType).
  372. Where("DATE_FORMAT(FROM_UNIXTIME(abortion_at),'%Y-%m') IN ?", dayTimeList)
  373. if req.Lact >= 0 && req.CowType == pasturePb.CowType_Breeding_Calf {
  374. pref2.Where("lact = ?", req.Lact)
  375. }
  376. if err = pref2.Group("month").
  377. Find(&cowAbortionMonthList).Error; err != nil {
  378. return nil, xerr.WithStack(err)
  379. }
  380. // 更新月份映射表中的流产数量和流产率
  381. for _, v := range cowAbortionMonthList {
  382. if table, exists := monthMap[v.Month]; exists {
  383. table.AbortionCount = v.CowCount
  384. if table.PregnantCount > 0 {
  385. table.AbortionRate = float32(util.RoundToTwoDecimals(float64(v.CowCount) / float64(table.PregnantCount) * 100))
  386. }
  387. }
  388. }
  389. chart := &pasturePb.AbortionRateChart{
  390. Header: make([]string, 0),
  391. AbortionCountMonth: make([]int32, 0),
  392. PregnantCountMonth: make([]int32, 0),
  393. AbortionRateMonth: make([]float32, 0),
  394. }
  395. table := make([]*pasturePb.AbortionRateTable, 0)
  396. for _, month := range dayTimeList {
  397. data := monthMap[month]
  398. chart.Header = append(chart.Header, month) // 添加月份到Header
  399. chart.AbortionCountMonth = append(chart.AbortionCountMonth, data.AbortionCount)
  400. chart.PregnantCountMonth = append(chart.PregnantCountMonth, data.PregnantCount)
  401. chart.AbortionRateMonth = append(chart.AbortionRateMonth, data.AbortionRate)
  402. table = append(table, data)
  403. }
  404. return &pasturePb.AbortionRateResponse{
  405. Code: http.StatusOK,
  406. Msg: "ok",
  407. Data: &pasturePb.AbortionRateData{
  408. Chart: chart,
  409. Table: table,
  410. },
  411. }, nil
  412. }