Browse Source

analysis: 栏舍行为数据

Yi 5 days ago
parent
commit
48aa454ae0
3 changed files with 150 additions and 33 deletions
  1. 2 0
      http/route/analysis_api.go
  2. 16 12
      model/pen_behavior.go
  3. 132 21
      module/crontab/pen_behavior.go

+ 2 - 0
http/route/analysis_api.go

@@ -25,5 +25,7 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		analysisRoute.POST("/sale/cow/report", analysis.SaleCowReport)                                // 销售牛牛报告
 		analysisRoute.POST("/single/factor/pregnant/report", analysis.SingleFactorInfantSurvivalRate) // 单因素受胎率
 		analysisRoute.POST("/multi/factor/pregnant/report", analysis.MultiFactorInfantSurvivalRate)   // 多因素受胎率
+
+		//analysisRoute.POST("/pen/behavior", analysis.SingleFactorInfantSurvivalRate)
 	}
 }

+ 16 - 12
model/pen_behavior.go

@@ -5,7 +5,7 @@ type PenBehavior struct {
 	PastureId      int64  `json:"pastureId"`
 	HeatDate       string `json:"heatDate"`
 	ActiveTime     string `json:"activeTime"`
-	PenId          int64  `json:"penId"`
+	PenId          int32  `json:"penId"`
 	PenName        string `json:"penName"`
 	CowCount       int32  `json:"cowCount"`
 	AvgHigh        int32  `json:"avgHigh"`
@@ -34,15 +34,19 @@ func (p *PenBehavior) TableName() string {
 }
 
 type PenBehaviorData struct {
-	PastureId int64  `json:"pastureId"`
-	PenId     int32  `json:"penId"`
-	PenName   string `json:"penName"`
-	HeatDate  string `json:"heatDate"`
-	Frameid   int32  `json:"frameid"`
-	CowCount  int32  `json:"cowCount"`
-	AvgHigh   int32  `json:"avgHigh"`
-	SumRumina int32  `json:"sumRumina"`
-	SumIntake int32  `json:"sumIntake"`
-	SumRest   int32  `json:"sumRest"`
-	SumGasp   int32  `json:"sumGasp"`
+	PastureId  int64  `json:"pastureId"`
+	PenId      int32  `json:"penId"`
+	PenName    string `json:"penName"`
+	HeatDate   string `json:"heatDate"`
+	Frameid    int32  `json:"frameid"`
+	CowCount   int32  `json:"cowCount"`
+	AvgHigh    int32  `json:"avgHigh"`
+	SumRumina  int32  `json:"sumRumina"`
+	SumIntake  int32  `json:"sumIntake"`
+	SumRest    int32  `json:"sumRest"`
+	SumGasp    int32  `json:"sumGasp"`
+	RuminaRate int32  `json:"ruminaRate"`
+	IntakeRate int32  `json:"intakeRate"`
+	RestRate   int32  `json:"restRate"`
+	GaspRate   int32  `json:"gaspRate"`
 }

+ 132 - 21
module/crontab/pen_behavior.go

@@ -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