package backend import ( "context" "fmt" "kpt-pasture/model" "kpt-pasture/util" "net/http" "sort" "time" "gitee.com/xuyiping_admin/pkg/xerr" pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow" ) func (s *StoreEntry) PenBehavior(ctx context.Context, req *pasturePb.BarnBehaviorCurveRequest) (*pasturePb.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 &pasturePb.BarnBehaviorCurveResponse{ Code: http.StatusOK, Msg: "ok", Data: model.PenBehaviorSlice(penBehaviorList).ToPB(), }, nil } func (s *StoreEntry) PenBehaviorDaily(ctx context.Context, req *pasturePb.BarnMonitorRequest) (*model.BarnMonitorResponse, error) { userModel, err := s.GetUserModel(ctx) if err != nil { return nil, xerr.WithStack(err) } if req.StartAt == 0 || req.EndAt == 0 || req.EndAt < req.StartAt { return nil, xerr.Customf("时间范围错误") } startDate := time.Unix(int64(req.StartAt), 0).Local().Format(model.LayoutDate2) endDate := time.Unix(int64(req.EndAt), 0).Local().Format(model.LayoutDate2) dataTimeRange, err := util.GetDaysBetween(startDate, endDate) if err != nil { return nil, xerr.WithStack(err) } headers := make([]string, 0) if req.PenId <= 0 && req.BehaviorKind > 0 { penList, _ := s.GetPenList(ctx, userModel.AppPasture.Id) for _, v := range penList { headers = append(headers, v.Name) } } else { behaviorEnumList := s.Behavior("") for _, v := range behaviorEnumList { headers = append(headers, v.Label) } headers = append(headers, "方差") } penBehaviorDayModelList := make([]*model.PenBehaviorDayModel, 0) pref := s.DB.Model(new(model.PenBehaviorDay)). Select(`distinct(heat_date) as heat_date,pen_id,pen_name,rumina_std,cow_count,day_rumina,day_intake, day_inactive,day_milk,day_rumina+day_intake as day_chew,24*60 - day_active as day_immobility`). Where("pasture_id = ?", userModel.AppPasture.Id). Where("heat_date BETWEEN ? AND ?", startDate, endDate) if req.PenId <= 0 && req.BehaviorKind > 0 { if err = pref.Group("heat_date,pen_id"). Order("heat_date,pen_id"). Find(&penBehaviorDayModelList).Error; err != nil { return nil, xerr.WithStack(err) } } else { if err = pref.Where("pen_id = ?", req.PenId). Order("heat_date"). Find(&penBehaviorDayModelList).Error; err != nil { return nil, xerr.WithStack(err) } } return &model.BarnMonitorResponse{ Code: http.StatusOK, Msg: "ok", Data: model.PenBehaviorDayModelSlice(penBehaviorDayModelList).ToPB(dataTimeRange, headers, req.BehaviorKind), }, 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 }