|
@@ -2,9 +2,11 @@ package backend
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "fmt"
|
|
|
"kpt-pasture/model"
|
|
|
"kpt-pasture/util"
|
|
|
"net/http"
|
|
|
+ "sort"
|
|
|
"time"
|
|
|
|
|
|
"gitee.com/xuyiping_admin/pkg/xerr"
|
|
@@ -39,6 +41,33 @@ func (s *StoreEntry) PenBehavior(ctx context.Context, req *pasturePb.BarnBehavio
|
|
|
}, nil
|
|
|
}
|
|
|
|
|
|
+func (s *StoreEntry) PenBehavior2(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*model.BarnBehaviorCurveResponse, error) {
|
|
|
+ userModel, err := s.GetUserModel(ctx)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt {
|
|
|
+ return nil, xerr.Customf("时间范围错误")
|
|
|
+ }
|
|
|
+ startTime := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2)
|
|
|
+ endTime := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2)
|
|
|
+ penBehaviorList := make([]*model.PenBehavior, 0)
|
|
|
+ if err = s.DB.Model(new(model.PenBehavior)).
|
|
|
+ Where("pasture_id = ?", userModel.AppPasture.Id).
|
|
|
+ Where("pen_id = ?", req.PenId).
|
|
|
+ Where("heat_date BETWEEN ? AND ?", startTime, endTime).
|
|
|
+ Find(&penBehaviorList).Error; err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return &model.BarnBehaviorCurveResponse{
|
|
|
+ Code: http.StatusOK,
|
|
|
+ Msg: "ok",
|
|
|
+ Data: model.PenBehaviorSlice(penBehaviorList).ToPB2(),
|
|
|
+ }, nil
|
|
|
+}
|
|
|
+
|
|
|
func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*pasturePb.BarnMonitorResponse, error) {
|
|
|
userModel, err := s.GetUserModel(ctx)
|
|
|
if err != nil {
|
|
@@ -82,3 +111,177 @@ func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMo
|
|
|
Data: model.PenBehaviorDayModelSlice(penBehaviorDayModelList).ToPB(dataTimeRange),
|
|
|
}, err
|
|
|
}
|
|
|
+
|
|
|
+func (s *StoreEntry) CowBehaviorDistribution(ctx context.Context, req *pasturePb.CowBehaviorDistributionRequest) (*pasturePb.CowBehaviorDistributionResponse, error) {
|
|
|
+ userModel, err := s.GetUserModel(ctx)
|
|
|
+ if err != nil {
|
|
|
+ return nil, xerr.WithStack(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验时间必须比当天时间小一天
|
|
|
+ if time.Now().Local().Format(model.LayoutDate2) == req.DateTime {
|
|
|
+ return nil, xerr.Customf("时间范围错误")
|
|
|
+ }
|
|
|
+
|
|
|
+ milkDailList := make([]*model.MilkDaily, 0)
|
|
|
+ pref := s.DB.Table(fmt.Sprintf("%s as a", new(model.Cow).TableName())).
|
|
|
+ Joins(fmt.Sprintf("JOIN %s AS b on a.id = b.cow_id", new(model.MilkDaily).TableName())).
|
|
|
+ Select("b.*").
|
|
|
+ Where("a.pasture_id = ?", userModel.AppPasture.Id).
|
|
|
+ Where("a.neck_ring_number != ?", "").
|
|
|
+ Where("a.sex = ?", pasturePb.Genders_Female).
|
|
|
+ Where("b.heat_date = ? ", req.DateTime).
|
|
|
+ Where("b.day_high > ?", 0)
|
|
|
+
|
|
|
+ if len(req.PenIds) > 0 {
|
|
|
+ pref.Where("a.pen_id IN (?)", req.PenIds)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err = pref.Order("b.breed_status,b.lactation_age").
|
|
|
+ Find(&milkDailList).Error; err != nil {
|
|
|
+ return nil, xerr.WithStack(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 未配 空怀 怀孕 配种
|
|
|
+ data := &pasturePb.CowBehaviorDistributionItem{
|
|
|
+ Headers: make([]string, 0),
|
|
|
+ Color: make([]string, 0),
|
|
|
+ MedianLine: make(map[string]float32),
|
|
|
+ CalvingAge: make([]int32, 0),
|
|
|
+ UnBreed: make([]*pasturePb.CowBehaviorData, 0),
|
|
|
+ Breed: make([]*pasturePb.CowBehaviorData, 0),
|
|
|
+ Pregnant: make([]*pasturePb.CowBehaviorData, 0),
|
|
|
+ Empty: make([]*pasturePb.CowBehaviorData, 0),
|
|
|
+ }
|
|
|
+
|
|
|
+ breedStatus := s.BreedStatusEnumList()
|
|
|
+ for _, v := range breedStatus {
|
|
|
+ if v.Value == int32(pasturePb.BreedStatus_Abort) ||
|
|
|
+ v.Value == int32(pasturePb.BreedStatus_Calving) ||
|
|
|
+ v.Value == int32(pasturePb.BreedStatus_No_Mating) ||
|
|
|
+ v.Value == int32(pasturePb.BreedStatus_Invalid) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ data.Headers = append(data.Headers, v.Label)
|
|
|
+ switch v.Label {
|
|
|
+ case "未配":
|
|
|
+ data.Color = append(data.Color, "#b53827")
|
|
|
+ case "空怀":
|
|
|
+ data.Color = append(data.Color, "#2784b5")
|
|
|
+ case "怀孕":
|
|
|
+ data.Color = append(data.Color, "#2757b5")
|
|
|
+ case "配种":
|
|
|
+ data.Color = append(data.Color, "#27b560")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(milkDailList) <= 0 {
|
|
|
+ return &pasturePb.CowBehaviorDistributionResponse{
|
|
|
+ Code: http.StatusOK,
|
|
|
+ Msg: "ok",
|
|
|
+ Data: data,
|
|
|
+ }, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, v := range milkDailList {
|
|
|
+ dayData := int32(0)
|
|
|
+ switch req.BehaviorKind {
|
|
|
+ case pasturePb.Behavior_Rumina:
|
|
|
+ dayData = v.DayRumina
|
|
|
+ case pasturePb.Behavior_Intake:
|
|
|
+ dayData = v.DayIntake
|
|
|
+ case pasturePb.Behavior_Reset:
|
|
|
+ dayData = v.DayInactive
|
|
|
+ case pasturePb.Behavior_Immobility:
|
|
|
+ dayData = 24*60 - v.DayActive
|
|
|
+ case pasturePb.Behavior_Chew:
|
|
|
+ dayData = v.DayRumina + v.DayIntake
|
|
|
+ }
|
|
|
+ switch v.BreedStatus {
|
|
|
+ case pasturePb.BreedStatus_Calving:
|
|
|
+ data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
|
|
|
+ EarNumber: v.EarNumber,
|
|
|
+ CalvingAge: v.LactationAge,
|
|
|
+ DayData: dayData,
|
|
|
+ })
|
|
|
+ case pasturePb.BreedStatus_Empty:
|
|
|
+ data.Empty = append(data.Empty, &pasturePb.CowBehaviorData{
|
|
|
+ EarNumber: v.EarNumber,
|
|
|
+ CalvingAge: v.LactationAge,
|
|
|
+ DayData: dayData,
|
|
|
+ })
|
|
|
+ case pasturePb.BreedStatus_UnBreed:
|
|
|
+ data.UnBreed = append(data.UnBreed, &pasturePb.CowBehaviorData{
|
|
|
+ EarNumber: v.EarNumber,
|
|
|
+ CalvingAge: v.LactationAge,
|
|
|
+ DayData: dayData,
|
|
|
+ })
|
|
|
+ case pasturePb.BreedStatus_Breeding:
|
|
|
+ data.Breed = append(data.Breed, &pasturePb.CowBehaviorData{
|
|
|
+ EarNumber: v.EarNumber,
|
|
|
+ CalvingAge: v.LactationAge,
|
|
|
+ DayData: dayData,
|
|
|
+ })
|
|
|
+ case pasturePb.BreedStatus_Pregnant:
|
|
|
+ data.Pregnant = append(data.Pregnant, &pasturePb.CowBehaviorData{
|
|
|
+ EarNumber: v.EarNumber,
|
|
|
+ CalvingAge: v.LactationAge,
|
|
|
+ DayData: dayData,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取Breed的中位数
|
|
|
+ if len(data.Breed) > 0 {
|
|
|
+ data.MedianLine["breed"] = float32(getMedian(data.Breed, func(p *pasturePb.CowBehaviorData) int {
|
|
|
+ return int(p.DayData)
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(data.Pregnant) > 0 {
|
|
|
+ data.MedianLine["pregnant"] = float32(getMedian(data.Pregnant, func(p *pasturePb.CowBehaviorData) int {
|
|
|
+ return int(p.DayData)
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(data.Empty) > 0 {
|
|
|
+ data.MedianLine["empty"] = float32(getMedian(data.Empty, func(p *pasturePb.CowBehaviorData) int {
|
|
|
+ return int(p.DayData)
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(data.UnBreed) > 0 {
|
|
|
+ data.MedianLine["unBreed"] = float32(getMedian(data.UnBreed, func(p *pasturePb.CowBehaviorData) int {
|
|
|
+ return int(p.DayData)
|
|
|
+ }))
|
|
|
+ }
|
|
|
+
|
|
|
+ return &pasturePb.CowBehaviorDistributionResponse{
|
|
|
+ Code: http.StatusOK,
|
|
|
+ Msg: "ok",
|
|
|
+ Data: data,
|
|
|
+ }, err
|
|
|
+}
|
|
|
+
|
|
|
+// 获取结构体切片中某个int字段的中位值
|
|
|
+func getMedian(dataList []*pasturePb.CowBehaviorData, getField func(*pasturePb.CowBehaviorData) int) float64 {
|
|
|
+ // 1. 提取字段值
|
|
|
+ values := make([]int, len(dataList))
|
|
|
+ for i, p := range dataList {
|
|
|
+ values[i] = getField(p)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 排序
|
|
|
+ sort.Ints(values)
|
|
|
+
|
|
|
+ // 3. 计算中位数
|
|
|
+ n := len(values)
|
|
|
+ if n == 0 {
|
|
|
+ return 0
|
|
|
+ }
|
|
|
+
|
|
|
+ if n%2 == 1 {
|
|
|
+ return float64(values[n/2])
|
|
|
+ }
|
|
|
+ return float64(values[n/2-1]+values[n/2]) / 2.0
|
|
|
+}
|