|  | @@ -9,27 +9,61 @@ import (
 | 
	
		
			
				|  |  |  )
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  // PenBehavior 栏舍行为曲线
 | 
	
		
			
				|  |  | -func (e *Entry) PenBehavior(pastureId int64, processIds []int64) {
 | 
	
		
			
				|  |  | -	neckRingOriginalList := make([]*model.NeckRingOriginal, 0)
 | 
	
		
			
				|  |  | +func (e *Entry) PenBehavior(pastureId int64, processIds []int64) error {
 | 
	
		
			
				|  |  | +	// 1. 获取颈环原始数据
 | 
	
		
			
				|  |  | +	neckRingOriginalList, err := e.getNeckRingOriginalList(pastureId, processIds)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return fmt.Errorf("获取颈环原始数据失败: %w", err)
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// 2. 获取牛只信息
 | 
	
		
			
				|  |  | +	cowMap, err := e.getCowMap(pastureId, neckRingOriginalList)
 | 
	
		
			
				|  |  | +	if err != nil {
 | 
	
		
			
				|  |  | +		return fmt.Errorf("获取牛只信息失败: %w", err)
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// 3. 处理栏舍行为数据
 | 
	
		
			
				|  |  | +	penData := e.processPenBehaviorData(neckRingOriginalList, cowMap)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// 4. 计算平均值和百分比
 | 
	
		
			
				|  |  | +	e.calculateAveragesAndRates(penData)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// 5. 保存数据
 | 
	
		
			
				|  |  | +	if err := e.savePenBehaviorData(penData); err != nil {
 | 
	
		
			
				|  |  | +		return fmt.Errorf("保存栏舍行为数据失败: %w", err)
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// getNeckRingOriginalList 获取颈环原始数据
 | 
	
		
			
				|  |  | +func (e *Entry) getNeckRingOriginalList(pastureId int64, processIds []int64) ([]*model.NeckRingOriginal, error) {
 | 
	
		
			
				|  |  | +	var neckRingOriginalList []*model.NeckRingOriginal
 | 
	
		
			
				|  |  |  	if err := e.DB.Model(new(model.NeckRingOriginal)).
 | 
	
		
			
				|  |  | -		Where("id IN (?)", processIds).
 | 
	
		
			
				|  |  | -		Where("pasture_id = ?", pastureId).
 | 
	
		
			
				|  |  | +		Where("id IN (?) AND pasture_id = ?", processIds, pastureId).
 | 
	
		
			
				|  |  |  		Order("heat_date,neck_ring_number,frameid").
 | 
	
		
			
				|  |  |  		Find(&neckRingOriginalList).Error; err != nil {
 | 
	
		
			
				|  |  | -		zaplog.Error("PenBehavior", zap.Any("error", err), zap.Any("processIds", processIds))
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +	return neckRingOriginalList, nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	cowIds := make([]int64, 0)
 | 
	
		
			
				|  |  | +// getCowMap 获取牛只信息映射
 | 
	
		
			
				|  |  | +func (e *Entry) getCowMap(pastureId int64, neckRingOriginalList []*model.NeckRingOriginal) (map[string]*model.Cow, error) {
 | 
	
		
			
				|  |  | +	// 提取牛只ID
 | 
	
		
			
				|  |  | +	cowIds := make([]int64, 0, len(neckRingOriginalList))
 | 
	
		
			
				|  |  |  	for _, v := range neckRingOriginalList {
 | 
	
		
			
				|  |  |  		cowIds = append(cowIds, v.Id)
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	// 获取牛只信息
 | 
	
		
			
				|  |  |  	cowInfoList, err := e.GetCowByIds(pastureId, cowIds)
 | 
	
		
			
				|  |  |  	if err != nil {
 | 
	
		
			
				|  |  | -		zaplog.Error("PenBehavior", zap.Any("error", err), zap.Any("cowIds", cowIds))
 | 
	
		
			
				|  |  | -		return
 | 
	
		
			
				|  |  | +		return nil, err
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	cowMap := make(map[string]*model.Cow)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// 构建牛只信息映射
 | 
	
		
			
				|  |  | +	cowMap := make(map[string]*model.Cow, len(cowInfoList))
 | 
	
		
			
				|  |  |  	for _, v := range cowInfoList {
 | 
	
		
			
				|  |  |  		if v.NeckRingNumber == "" {
 | 
	
		
			
				|  |  |  			continue
 | 
	
	
		
			
				|  | @@ -37,18 +71,30 @@ func (e *Entry) PenBehavior(pastureId int64, processIds []int64) {
 | 
	
		
			
				|  |  |  		cowMap[v.NeckRingNumber] = v
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -	penData := make(map[string]*model.PenBehaviorData)
 | 
	
		
			
				|  |  | +	return cowMap, nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// processPenBehaviorData 处理栏舍行为数据
 | 
	
		
			
				|  |  | +func (e *Entry) processPenBehaviorData(neckRingOriginalList []*model.NeckRingOriginal, cowMap map[string]*model.Cow) map[string]*model.PenBehaviorData {
 | 
	
		
			
				|  |  | +	penData := make(map[string]*model.PenBehaviorData, len(neckRingOriginalList))
 | 
	
		
			
				|  |  |  	for _, v := range neckRingOriginalList {
 | 
	
		
			
				|  |  |  		cowInfo, ok := cowMap[v.NeckRingNumber]
 | 
	
		
			
				|  |  |  		if !ok {
 | 
	
		
			
				|  |  | -			zaplog.Error("PenBehavior", zap.Any("error", err), zap.Any("neckRingNumber", v.NeckRingNumber))
 | 
	
		
			
				|  |  | +			zaplog.Error("PenBehavior", zap.Any("neckRingNumber", v.NeckRingNumber))
 | 
	
		
			
				|  |  |  			continue
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  		key := fmt.Sprintf("%s_%d_%d", v.ActiveDate, cowInfo.PenId, v.Frameid)
 | 
	
		
			
				|  |  | -		if penData[key] == nil {
 | 
	
		
			
				|  |  | +		if data, exists := penData[key]; exists {
 | 
	
		
			
				|  |  | +			data.CowCount++
 | 
	
		
			
				|  |  | +			data.AvgHigh += v.High
 | 
	
		
			
				|  |  | +			data.SumRumina += ifThenElse(v.Rumina >= 8, 1, 0)
 | 
	
		
			
				|  |  | +			data.SumIntake += ifThenElse(v.Intake >= 8, 1, 0)
 | 
	
		
			
				|  |  | +			data.SumRest += ifThenElse(v.Inactive >= 8, 1, 0)
 | 
	
		
			
				|  |  | +			data.SumGasp += ifThenElse(v.Gasp >= 8, 1, 0)
 | 
	
		
			
				|  |  | +		} else {
 | 
	
		
			
				|  |  |  			penData[key] = &model.PenBehaviorData{
 | 
	
		
			
				|  |  | -				PastureId: pastureId,
 | 
	
		
			
				|  |  | +				PastureId: cowInfo.PastureId,
 | 
	
		
			
				|  |  |  				PenId:     cowInfo.PenId,
 | 
	
		
			
				|  |  |  				PenName:   cowInfo.PenName,
 | 
	
		
			
				|  |  |  				HeatDate:  v.ActiveDate,
 | 
	
	
		
			
				|  | @@ -56,22 +102,87 @@ func (e *Entry) PenBehavior(pastureId int64, processIds []int64) {
 | 
	
		
			
				|  |  |  				CowCount:  1,
 | 
	
		
			
				|  |  |  				AvgHigh:   v.High,
 | 
	
		
			
				|  |  |  			}
 | 
	
		
			
				|  |  | -		} else {
 | 
	
		
			
				|  |  | -			penData[key].CowCount++
 | 
	
		
			
				|  |  | -			penData[key].AvgHigh += v.High
 | 
	
		
			
				|  |  | -			penData[key].SumRumina += ifThenElse(v.Rumina >= 8, 1, 0)
 | 
	
		
			
				|  |  | -			penData[key].SumIntake += ifThenElse(v.Intake >= 8, 1, 0)
 | 
	
		
			
				|  |  | -			penData[key].SumRest += ifThenElse(v.Inactive >= 8, 1, 0)
 | 
	
		
			
				|  |  | -			penData[key].SumGasp += ifThenElse(v.Gasp >= 8, 1, 0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | -	// 计算平均值
 | 
	
		
			
				|  |  | +	return penData
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// calculateAveragesAndRates 计算平均值和百分比
 | 
	
		
			
				|  |  | +func (e *Entry) calculateAveragesAndRates(penData map[string]*model.PenBehaviorData) {
 | 
	
		
			
				|  |  |  	for _, data := range penData {
 | 
	
		
			
				|  |  | +		// 计算平均值
 | 
	
		
			
				|  |  |  		data.AvgHigh = data.AvgHigh / data.CowCount
 | 
	
		
			
				|  |  | +		
 | 
	
		
			
				|  |  | +		// 计算百分比
 | 
	
		
			
				|  |  | +		if data.CowCount > 0 {
 | 
	
		
			
				|  |  | +			data.RuminaRate = int32(float64(data.SumRumina) / float64(data.CowCount) * 100)
 | 
	
		
			
				|  |  | +			data.IntakeRate = int32(float64(data.SumIntake) / float64(data.CowCount) * 100)
 | 
	
		
			
				|  |  | +			data.RestRate = int32(float64(data.SumRest) / float64(data.CowCount) * 100)
 | 
	
		
			
				|  |  | +			data.GaspRate = int32(float64(data.SumGasp) / float64(data.CowCount) * 100)
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// savePenBehaviorData 保存栏舍行为数据
 | 
	
		
			
				|  |  | +func (e *Entry) savePenBehaviorData(penData map[string]*model.PenBehaviorData) error {
 | 
	
		
			
				|  |  | +	for _, data := range penData {
 | 
	
		
			
				|  |  | +		// 构建活动时间
 | 
	
		
			
				|  |  | +		activeTime := e.calculateActiveTime(data.HeatDate, data.Frameid)
 | 
	
		
			
				|  |  | +		
 | 
	
		
			
				|  |  | +		// 构建保存数据
 | 
	
		
			
				|  |  | +		penBehavior := &model.PenBehavior{
 | 
	
		
			
				|  |  | +			PastureId:  data.PastureId,
 | 
	
		
			
				|  |  | +			HeatDate:   data.HeatDate,
 | 
	
		
			
				|  |  | +			ActiveTime: activeTime,
 | 
	
		
			
				|  |  | +			PenId:      data.PenId,
 | 
	
		
			
				|  |  | +			PenName:    data.PenName,
 | 
	
		
			
				|  |  | +			CowCount:   data.CowCount,
 | 
	
		
			
				|  |  | +			AvgHigh:    data.AvgHigh,
 | 
	
		
			
				|  |  | +			SumRumina:  data.SumRumina,
 | 
	
		
			
				|  |  | +			SumIntake:  data.SumIntake,
 | 
	
		
			
				|  |  | +			SumRest:    data.SumRest,
 | 
	
		
			
				|  |  | +			SumGasp:    data.SumGasp,
 | 
	
		
			
				|  |  | +			RuminaRate: data.RuminaRate,
 | 
	
		
			
				|  |  | +			IntakeRate: data.IntakeRate,
 | 
	
		
			
				|  |  | +			RestRate:   data.RestRate,
 | 
	
		
			
				|  |  | +			GaspRate:   data.GaspRate,
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		// 使用 Upsert 操作
 | 
	
		
			
				|  |  | +		if err := e.DB.Model(new(model.PenBehavior)).
 | 
	
		
			
				|  |  | +			Where("pasture_id = ? AND heat_date = ? AND pen_id = ? AND active_time = ?",
 | 
	
		
			
				|  |  | +				penBehavior.PastureId, penBehavior.HeatDate, penBehavior.PenId, penBehavior.ActiveTime).
 | 
	
		
			
				|  |  | +			Assign(map[string]interface{}{
 | 
	
		
			
				|  |  | +				"cow_count":    penBehavior.CowCount,
 | 
	
		
			
				|  |  | +				"avg_high":     penBehavior.AvgHigh,
 | 
	
		
			
				|  |  | +				"sum_rumina":   penBehavior.SumRumina,
 | 
	
		
			
				|  |  | +				"sum_intake":   penBehavior.SumIntake,
 | 
	
		
			
				|  |  | +				"sum_rest":     penBehavior.SumRest,
 | 
	
		
			
				|  |  | +				"sum_gasp":     penBehavior.SumGasp,
 | 
	
		
			
				|  |  | +				"rumina_rate":  penBehavior.RuminaRate,
 | 
	
		
			
				|  |  | +				"intake_rate":  penBehavior.IntakeRate,
 | 
	
		
			
				|  |  | +				"rest_rate":    penBehavior.RestRate,
 | 
	
		
			
				|  |  | +				"gasp_rate":    penBehavior.GaspRate,
 | 
	
		
			
				|  |  | +			}).
 | 
	
		
			
				|  |  | +			FirstOrCreate(penBehavior).Error; err != nil {
 | 
	
		
			
				|  |  | +			return err
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +	return nil
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// calculateActiveTime 计算活动时间
 | 
	
		
			
				|  |  | +func (e *Entry) calculateActiveTime(heatDate string, frameid int32) string {
 | 
	
		
			
				|  |  | +	// 计算小时和分钟
 | 
	
		
			
				|  |  | +	hour := (frameid / 10) * 2
 | 
	
		
			
				|  |  | +	minute := (frameid % 10) * 20 - 1
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +	// 构建时间字符串
 | 
	
		
			
				|  |  | +	return fmt.Sprintf("%s %02d:%02d", heatDate, hour, minute)
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +// ifThenElse 条件判断函数
 | 
	
		
			
				|  |  |  func ifThenElse(condition bool, a, b int32) int32 {
 | 
	
		
			
				|  |  |  	if condition {
 | 
	
		
			
				|  |  |  		return a
 |