analysis.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. package backend
  2. import (
  3. "context"
  4. "fmt"
  5. "kpt-pasture/model"
  6. "kpt-pasture/util"
  7. "net/http"
  8. "strings"
  9. "time"
  10. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  11. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  12. "gitee.com/xuyiping_admin/pkg/xerr"
  13. "go.uber.org/zap"
  14. )
  15. // GrowthCurve 生长曲线 获取图表数据
  16. func (s *StoreEntry) GrowthCurve(ctx context.Context, req *pasturePb.SearchGrowthCurvesRequest) (*pasturePb.GrowthCurvesResponse, error) {
  17. // 查询数据
  18. cowList := make([]*model.Cow, 0)
  19. pref := s.DB.Model(new(model.Cow)).Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  20. if req.GetCowId() != "" {
  21. pref.Where("id IN ?", strings.Split(req.CowId, ","))
  22. }
  23. if len(req.BirthDate) == 2 && req.BirthDate[0] != "" && req.BirthDate[1] != "" {
  24. t0, _ := time.Parse(time.RFC3339, req.BirthDate[0])
  25. t1, _ := time.Parse(time.RFC3339, req.BirthDate[1])
  26. pref.Where("birth_at BETWEEN ? AND ?", t0.Unix(), t1.Unix()+86399)
  27. }
  28. if err := pref.Find(&cowList).Error; err != nil {
  29. return nil, err
  30. }
  31. penList, err := s.GetPenList(ctx)
  32. if err != nil {
  33. return nil, xerr.WithStack(err)
  34. }
  35. // 计算图表数据
  36. chartsList := &pasturePb.Charts{
  37. CowId: make([]int32, 0),
  38. Weight: make([]float32, 0),
  39. DayAge: make([]int32, 0),
  40. }
  41. cowData := make([]*pasturePb.CowList, 0)
  42. for _, cow := range cowList {
  43. currentWeight := float32(cow.CurrentWeight) / 100
  44. penName := ""
  45. for _, v := range penList {
  46. if cow.PenId != v.Id {
  47. continue
  48. }
  49. penName = v.Name
  50. }
  51. cowData = append(cowData, &pasturePb.CowList{
  52. CowId: int32(cow.Id),
  53. EarNumber: cow.EarNumber,
  54. DayAge: cow.GetDayAge(),
  55. PenName: penName,
  56. CurrentWeight: currentWeight,
  57. BirthAt: int32(cow.BirthAt),
  58. BirthWeight: float32(cow.BirthWeight) / 100,
  59. LastWeightAt: int32(cow.LastWeightAt),
  60. DailyWeightGain: float32(cow.GetDayWeight() / 100),
  61. AverageDailyWeightGain: float32(cow.GetAverageDailyWeight() / 100),
  62. })
  63. chartsList.CowId = append(chartsList.CowId, int32(cow.Id))
  64. chartsList.Weight = append(chartsList.Weight, currentWeight)
  65. chartsList.DayAge = append(chartsList.DayAge, cow.GetDayAge())
  66. }
  67. // 返回数据
  68. return &pasturePb.GrowthCurvesResponse{
  69. Code: http.StatusOK,
  70. Message: "success",
  71. Data: &pasturePb.GrowthCurveData{
  72. Table: cowData,
  73. Charts: chartsList,
  74. },
  75. }, nil
  76. }
  77. func (s *StoreEntry) WeightRange(ctx context.Context, req *pasturePb.WeightRangeRequest) (*pasturePb.WeightRangeResponse, error) {
  78. cowWeightRange := make([]*model.CowWeightRange, 0)
  79. prefix := s.DB.Model(new(model.Cow)).Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  80. if req.CowKind > 0 {
  81. prefix.Where("cow_kind = ?", req.CowKind)
  82. }
  83. if err := prefix.Select(`
  84. CASE
  85. WHEN current_weight BETWEEN 0 AND 50000 THEN '0-50'
  86. WHEN current_weight BETWEEN 50001 AND 100000 THEN '51-100'
  87. WHEN current_weight BETWEEN 100001 AND 150000 THEN '101-150'
  88. WHEN current_weight BETWEEN 150001 AND 200000 THEN '151-200'
  89. WHEN current_weight BETWEEN 200001 AND 250000 THEN '201-250'
  90. WHEN current_weight BETWEEN 250001 AND 300000 THEN '251-300'
  91. WHEN current_weight BETWEEN 300001 AND 350000 THEN '301-350'
  92. WHEN current_weight BETWEEN 350001 AND 400000 THEN '351-400'
  93. WHEN current_weight BETWEEN 400001 AND 450000 THEN '401-450'
  94. WHEN current_weight BETWEEN 450001 AND 500000 THEN '451-500'
  95. WHEN current_weight BETWEEN 500001 AND 550000 THEN '500-550'
  96. WHEN current_weight BETWEEN 550001 AND 600000 THEN '551-600'
  97. WHEN current_weight BETWEEN 600001 AND 650000 THEN '601-650'
  98. WHEN current_weight BETWEEN 650001 AND 700000 THEN '651-700'
  99. WHEN current_weight BETWEEN 700001 AND 750000 THEN '701-750'
  100. ELSE '750+'
  101. END AS weight_range,
  102. COUNT(*) AS count `,
  103. ).Group("weight_range").Order("MIN(current_weight)").Find(&cowWeightRange).Error; err != nil {
  104. return nil, err
  105. }
  106. if len(cowWeightRange) == 0 {
  107. return &pasturePb.WeightRangeResponse{
  108. Code: http.StatusOK,
  109. Message: "ok",
  110. Data: &pasturePb.WeightRangeData{
  111. CowList: make([]*pasturePb.CowList, 0),
  112. WeightBarChart: &pasturePb.WeightBarChart{
  113. Header: make([]string, 0),
  114. Data: make([]int32, 0),
  115. },
  116. },
  117. }, nil
  118. }
  119. header := make([]string, 0)
  120. data := make([]int32, 0)
  121. for _, v := range cowWeightRange {
  122. header = append(header, v.WeightRange)
  123. data = append(data, v.Count)
  124. }
  125. // 牛只详情列表
  126. pref := s.DB.Model(new(model.Cow)).Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  127. if req.CowKind > 0 {
  128. pref.Where("cow_kind = ?", req.CowKind)
  129. }
  130. cowList := make([]*model.Cow, 0)
  131. if req.MinWeight >= 0 && req.MaxWeight >= 0 && req.MinWeight < req.MaxWeight {
  132. pref.Where("current_weight BETWEEN ? AND ? ", req.MinWeight*1000, req.MaxWeight*1000)
  133. }
  134. if err := pref.Find(&cowList).Error; err != nil {
  135. return nil, err
  136. }
  137. penMap := s.PenMap(ctx)
  138. return &pasturePb.WeightRangeResponse{
  139. Code: http.StatusOK,
  140. Message: "ok",
  141. Data: &pasturePb.WeightRangeData{
  142. CowList: model.CowSlice(cowList).WeightRangeToPB(penMap),
  143. WeightBarChart: &pasturePb.WeightBarChart{
  144. Header: header,
  145. Data: data,
  146. },
  147. },
  148. }, nil
  149. }
  150. func (s *StoreEntry) MatingTimely(ctx context.Context, req *pasturePb.MatingTimelyRequest) (*model.MatingTimelyResponse, error) {
  151. matingTimelyChart := make([]*model.MatingTimelyChart, 0)
  152. sql := `SELECT calving_age,cow_type, DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m-%d') AS reality_day, lact_group
  153. FROM (
  154. SELECT calving_age, cow_type,reality_day, '0' AS lact_group
  155. FROM event_mating
  156. WHERE lact = 0 AND status = 1
  157. UNION ALL
  158. SELECT calving_age,cow_type, reality_day, '1' AS lact_group
  159. FROM event_mating
  160. WHERE lact = 1 AND status = 1
  161. UNION ALL
  162. SELECT calving_age,cow_type, reality_day, '2' AS lact_group
  163. FROM event_mating
  164. WHERE lact = 2 AND status = 1
  165. UNION ALL
  166. SELECT calving_age, cow_type, reality_day, '3+' AS lact_group
  167. FROM event_mating
  168. WHERE lact >= 3 AND status = 1
  169. ) AS subquery WHERE 1 = 1 `
  170. whereSql := ""
  171. if req.CowType > 0 {
  172. whereSql += fmt.Sprintf("AND cow_type = %d ", req.CowType)
  173. }
  174. if req.StartDayAt > 0 && req.EndDayAt > 0 {
  175. whereSql += fmt.Sprintf("AND reality_day BETWEEN %d AND %d", req.StartDayAt, req.EndDayAt)
  176. }
  177. if err := s.DB.Raw(fmt.Sprintf("%s %s", sql, whereSql)).Find(&matingTimelyChart).Error; err != nil {
  178. return nil, err
  179. }
  180. chart := &model.CowMatingChart{
  181. Lact0: make([][]string, 0),
  182. Lact1: make([][]string, 0),
  183. Lact2: make([][]string, 0),
  184. Lact3: make([][]string, 0),
  185. }
  186. if len(matingTimelyChart) == 0 {
  187. return &model.MatingTimelyResponse{
  188. Code: http.StatusOK,
  189. Message: "ok",
  190. Data: &model.MatingTimelyData{
  191. CowList: make([]*pasturePb.CowList, 0),
  192. Chart: chart,
  193. },
  194. }, nil
  195. }
  196. for _, v := range matingTimelyChart {
  197. t, _ := time.Parse(model.LayoutDate2, v.RealityDay)
  198. switch v.LactGroup {
  199. case "0":
  200. chart.Lact0 = append(chart.Lact0, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  201. case "1":
  202. chart.Lact1 = append(chart.Lact1, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  203. case "2":
  204. chart.Lact2 = append(chart.Lact2, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  205. case "3+":
  206. chart.Lact3 = append(chart.Lact3, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})
  207. }
  208. }
  209. // 牛只详情列表
  210. eventMatingList := make([]*model.EventMating, 0)
  211. pref := s.DB.Model(new(model.EventMating)).
  212. Where("status = ?", pasturePb.IsShow_Ok)
  213. if req.CowType > 0 {
  214. pref.Where("cow_type = ?", req.CowType)
  215. }
  216. if req.StartDayAt > 0 && req.EndDayAt > 0 {
  217. pref.Where("reality_day BETWEEN ? AND ?", req.StartDayAt, req.EndDayAt)
  218. }
  219. if err := pref.Find(&eventMatingList).Error; err != nil {
  220. return nil, err
  221. }
  222. return &model.MatingTimelyResponse{
  223. Code: http.StatusOK,
  224. Message: "ok",
  225. Data: &model.MatingTimelyData{
  226. CowList: model.EventMatingSlice(eventMatingList).ToPB2(),
  227. Chart: chart,
  228. },
  229. }, nil
  230. }
  231. func (s *StoreEntry) PenWeight(ctx context.Context, req *pasturePb.PenWeightRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PenWeightResponse, error) {
  232. penWeightList := make([]*model.PenWeight, 0)
  233. pref := s.DB.Model(new(model.Cow)).
  234. Select(`
  235. pen_id,
  236. CEILING(AVG(current_weight) / 1000 ) AS avg_weight,
  237. CEILING(SUM(current_weight) / 1000 ) AS all_weight,
  238. COUNT(*) AS cow_count`,
  239. ).Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  240. if len(req.PenId) > 0 && req.BarId <= 0 {
  241. pref.Where("pen_id IN ?", req.PenId)
  242. }
  243. if err := pref.Group("pen_id").
  244. Order("pen_id").
  245. Find(&penWeightList).Error; err != nil {
  246. return nil, err
  247. }
  248. chart := &pasturePb.PenWeightChart{
  249. Header: make([]string, 0),
  250. AllWeight: make([]int32, 0),
  251. AvgWeight: make([]int32, 0),
  252. CowCount: make([]int32, 0),
  253. }
  254. if len(penWeightList) <= 0 {
  255. return &pasturePb.PenWeightResponse{
  256. Code: http.StatusOK,
  257. Message: "ok",
  258. Data: &pasturePb.PenWeightData{
  259. CowList: make([]*pasturePb.CowList, 0),
  260. Chart: chart,
  261. },
  262. }, nil
  263. }
  264. cowList := make([]*model.Cow, 0)
  265. var count int64 = 0
  266. prefList := s.DB.Model(new(model.Cow)).
  267. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission)
  268. if len(req.PenId) <= 0 && req.BarId > 0 {
  269. prefList.Where("pen_id IN ?", []int32{req.BarId})
  270. } else {
  271. prefList.Where("pen_id IN ?", req.PenId)
  272. }
  273. if err := prefList.Count(&count).Limit(int(pagination.PageSize)).
  274. Offset(int(pagination.PageOffset)).Order("pen_id").
  275. Find(&cowList).Error; err != nil {
  276. return nil, xerr.WithStack(err)
  277. }
  278. penMap := s.PenMap(ctx)
  279. return &pasturePb.PenWeightResponse{
  280. Code: http.StatusOK,
  281. Message: "ok",
  282. Data: &pasturePb.PenWeightData{
  283. CowList: model.CowSlice(cowList).ToPB2(penMap, penWeightList),
  284. Total: int32(count),
  285. Page: pagination.Page,
  286. PageSize: pagination.PageSize,
  287. Chart: model.PenWeightSlice(penWeightList).ToPB(penMap),
  288. },
  289. }, nil
  290. }
  291. func (s *StoreEntry) AbortionRate(ctx context.Context, req *pasturePb.AbortionRateRequest) (*pasturePb.AbortionRateResponse, error) {
  292. dayTimeList, err := util.GetMonthsInRange(req.StartDayTime, req.EndDayTime)
  293. if err != nil {
  294. return nil, xerr.WithStack(err)
  295. }
  296. lastDayForMonth := make([]string, 0)
  297. for _, v := range dayTimeList {
  298. lastDayTime, _ := util.GetLastDayOfMonth(v)
  299. lastDayForMonth = append(lastDayForMonth, lastDayTime)
  300. }
  301. // 历史每月怀孕牛头数量
  302. cowPregnantMonthList := make([]*model.CowPregnantMonth, 0)
  303. pref := s.DB.Model(new(model.CowPregnant)).
  304. Select(`
  305. COUNT(cow_id) AS cow_count,
  306. DATE_FORMAT(FROM_UNIXTIME(created_at),'%Y-%m') as month`,
  307. ).Where("cow_type = ?", req.CowType).
  308. Where("DATE_FORMAT(FROM_UNIXTIME(`created_at`),'%Y-%m-%d') IN ?", lastDayForMonth)
  309. if req.Lact >= 0 && req.Lact <= 3 {
  310. pref.Where("lact = ?", req.Lact)
  311. } else {
  312. pref.Where("lact > ?", req.Lact)
  313. }
  314. if err = pref.Group("month").
  315. Find(&cowPregnantMonthList).Error; err != nil {
  316. return nil, xerr.WithStack(err)
  317. }
  318. // 历史每月流产牛头数量
  319. cowAbortionMonthList := make([]*model.CowPregnantMonth, 0)
  320. pref2 := s.DB.Model(new(model.EventAbortion)).
  321. Select(`
  322. COUNT(cow_id) AS cow_count,
  323. DATE_FORMAT(FROM_UNIXTIME(abortion_at),'%Y-%m') as month`,
  324. ).Where("cow_type = ?", req.CowType).
  325. Where("DATE_FORMAT(FROM_UNIXTIME(`abortion_at`),'%Y-%m') IN ?", dayTimeList)
  326. if req.Lact >= 0 {
  327. pref2.Where("lact = ?", req.Lact)
  328. }
  329. if err = pref2.Group("month").Find(&cowAbortionMonthList).Error; err != nil {
  330. return nil, xerr.WithStack(err)
  331. }
  332. chart := &pasturePb.AbortionRateChart{
  333. Header: make([]string, 0),
  334. AbortionCountMonth: make([]int32, 0),
  335. PregnantCountMonth: make([]int32, 0),
  336. AbortionRateMonth: make([]float32, 0),
  337. }
  338. table := make([]*pasturePb.AbortionRateTable, 0)
  339. for _, v2 := range cowAbortionMonthList {
  340. pregnantCountMonth := int32(0)
  341. for _, v := range cowPregnantMonthList {
  342. if v.Month == v2.Month {
  343. pregnantCountMonth = v.CowCount
  344. }
  345. }
  346. abortionRateMonth := float64(0)
  347. if pregnantCountMonth > 0 && v2.CowCount > 0 {
  348. abortionRateMonth = util.RoundToTwoDecimals(float64(v2.CowCount) / float64(pregnantCountMonth) * 100)
  349. }
  350. chart.Header = append(chart.Header, v2.Month)
  351. chart.AbortionCountMonth = append(chart.AbortionCountMonth, v2.CowCount)
  352. chart.PregnantCountMonth = append(chart.PregnantCountMonth, pregnantCountMonth)
  353. chart.AbortionRateMonth = append(chart.AbortionRateMonth, float32(abortionRateMonth))
  354. table = append(table, &pasturePb.AbortionRateTable{
  355. AbortionCount: v2.CowCount,
  356. MonthName: v2.Month,
  357. PregnantCount: pregnantCountMonth,
  358. AbortionRate: float32(abortionRateMonth),
  359. })
  360. }
  361. return &pasturePb.AbortionRateResponse{
  362. Code: http.StatusOK,
  363. Message: "ok",
  364. Data: &pasturePb.AbortionRateData{
  365. Chart: chart,
  366. Table: table,
  367. },
  368. }, nil
  369. }
  370. func (s *StoreEntry) TwentyOnePregnantRate(ctx context.Context, req *pasturePb.TwentyOnePregnantRateRequest) (*pasturePb.TwentyOnePregnantRateResponse, error) {
  371. startUnix := util.TimeParseLocalUnix(req.StartDate)
  372. endUnix := util.TimeParseLocalUnix(req.EndDate)
  373. if startUnix > endUnix {
  374. return nil, xerr.Customf("开始时间不能大于结束时间: %s ~ %d", req.StartDate, req.EndDate)
  375. }
  376. nowDateTime := time.Now()
  377. if endUnix > nowDateTime.Unix() {
  378. return nil, xerr.Customf("结束时间不能大于当前时间: %s ~ %s", req.EndDate, nowDateTime.Format(model.LayoutDate2))
  379. }
  380. dataRange, err := util.Get21DayPeriods(req.StartDate, req.EndDate)
  381. if err != nil {
  382. return nil, xerr.WithStack(err)
  383. }
  384. chart := &pasturePb.TwentyOnePregnantRateChart{
  385. Header: make([]string, 0),
  386. PregnantRate: make([]float32, 0),
  387. MatingRate: make([]float32, 0),
  388. }
  389. // 牛只主动停配期
  390. systemBasicName := ""
  391. switch req.CowType {
  392. case pasturePb.CowType_Breeding_Calf:
  393. systemBasicName = model.ProactivelyStopBreedingForAdult
  394. case pasturePb.CowType_Reserve_Calf:
  395. systemBasicName = model.ProactivelyStopBreedingForBackup
  396. default:
  397. return nil, xerr.Customf("不支持的牛只类型: %d", req.CowType)
  398. }
  399. systemBasic, err := s.GetSystemBasicByName(ctx, systemBasicName)
  400. if err != nil {
  401. return nil, xerr.WithStack(err)
  402. }
  403. stopBreedingDay := systemBasic.MinValue * 86400
  404. dateCowList := make([][]*model.Cow, len(dataRange))
  405. twentyOnePregnantRateList := make([]*pasturePb.TwentyOnePregnantRateList, 0)
  406. for i, v := range dataRange {
  407. middleDay, err := util.GetRangeDayMiddleDay(v, 11)
  408. if err != nil {
  409. return nil, xerr.WithStack(err)
  410. }
  411. middleDayUnix := util.TimeParseLocalEndUnix(middleDay)
  412. chart.Header = append(chart.Header, fmt.Sprintf("%s ~ %s", v[0], v[1]))
  413. cowList := s.TwentyonePregnantCowList(ctx, req.CowType, stopBreedingDay, middleDayUnix, []int64{})
  414. twentyOnePregnantRateList = append(twentyOnePregnantRateList, &pasturePb.TwentyOnePregnantRateList{
  415. StartDay: v[0],
  416. EndDay: v[1],
  417. ShouldBreedCount: int32(len(cowList)),
  418. RealityBreedCount: 0,
  419. BreedRate: 0,
  420. ShouldPregnantCount: 0,
  421. RealityPregnantCount: 0,
  422. PregnantRate: 0,
  423. RealityAbortionCount: 0,
  424. AbortionRate: 0,
  425. })
  426. dateCowList[i] = cowList
  427. }
  428. return &pasturePb.TwentyOnePregnantRateResponse{
  429. Code: http.StatusOK,
  430. Message: "ok",
  431. Data: &pasturePb.TwentyOnePregnantRateData{
  432. Chart: chart,
  433. Table: &pasturePb.TwentyOnePregnantRateTable{
  434. List: twentyOnePregnantRateList,
  435. Total: int32(len(dataRange)),
  436. },
  437. },
  438. }, nil
  439. }
  440. // TwentyonePregnantCowList 21天牛只停配期牛只列表
  441. func (s *StoreEntry) TwentyonePregnantCowList(
  442. ctx context.Context,
  443. cowType pasturePb.CowType_Kind,
  444. stopBreedingDay int32,
  445. middleDay int64,
  446. notInCow []int64,
  447. ) []*model.Cow {
  448. cowList := make([]*model.Cow, 0)
  449. switch cowType {
  450. case pasturePb.CowType_Reserve_Calf:
  451. pref := s.DB.Model(new(model.Cow)).
  452. Where("cow_type = ?", cowType).
  453. Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
  454. Where("is_pregnant = ?", pasturePb.IsShow_No).
  455. Where("lact = ?", 0).
  456. Where("birth_at + ? < ?", stopBreedingDay, middleDay)
  457. if len(notInCow) > 0 {
  458. pref = pref.Where("id NOT IN ?", notInCow)
  459. }
  460. if err := pref.Find(&cowList).Error; err != nil {
  461. zaplog.Error("TwentyonePregnantCowList",
  462. zap.Any("cowType", cowType),
  463. zap.Any("stopBreedingDay", stopBreedingDay),
  464. zap.Any("middleDay", middleDay),
  465. zap.Any("notInCow", notInCow),
  466. )
  467. }
  468. case pasturePb.CowType_Breeding_Calf:
  469. }
  470. return cowList
  471. }