소스 검색

analysis: 多维度受胎率分析

Yi 4 달 전
부모
커밋
71bcacc40d
11개의 변경된 파일236개의 추가작업 그리고 35개의 파일을 삭제
  1. 1 1
      go.mod
  2. 6 0
      go.sum
  3. 2 2
      model/event_mating.go
  4. 103 31
      module/backend/analysis_breed.go
  5. 1 1
      module/backend/calendar.go
  6. 34 0
      module/backend/config_data_breed.go
  7. 12 0
      module/backend/enum_map.go
  8. 1 0
      module/backend/enum_options.go
  9. 2 0
      module/backend/event_breed.go
  10. 40 0
      util/util.go
  11. 34 0
      util/util_test.go

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241105074912-bc99391ff5e0
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20241106025935-173da25922b3
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241029095841-aa1fe89b557a
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eko/gocache v1.1.0

+ 6 - 0
go.sum

@@ -48,6 +48,12 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20241105012623-f1bbf6690530 h1:XYf4/IuG
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241105012623-f1bbf6690530/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241105074912-bc99391ff5e0 h1:YDGgqXp0ehW+aNEmf/vL2b6MWLp1hGbLvsVXe2Q7j2s=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241105074912-bc99391ff5e0/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241106022913-38a1080b12ee h1:rIiYzv6+miuBQErl20p9i1h0XcHFxLJLFWXEZAmJr6o=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241106022913-38a1080b12ee/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241106023724-b2d5b980b9ea h1:4Q2ydO/Q4ydh9CDNiP7pUQoLiaOagrxkGOtMIhQAa7k=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241106023724-b2d5b980b9ea/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241106025935-173da25922b3 h1:BfF/ElvARXoyn7hykF1tjzu9kCkW8SvMcZNPMj31qpQ=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241106025935-173da25922b3/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241029095841-aa1fe89b557a h1:z6Pp4HmdcxEZ43avmbFoE3vwEYcWnIefqEEZZWCHfek=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241029095841-aa1fe89b557a/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 2 - 2
model/event_mating.go

@@ -19,7 +19,7 @@ type EventMating struct {
 	CalvingAt         int64                           `json:"calvingAt"`
 	RealityDay        int64                           `json:"realityDay"`
 	Status            pasturePb.IsShow_Kind           `json:"status"`
-	MatingNumber      int64                           `json:"matingNumber"`
+	MatingTimes       int32                           `json:"matingTimes"`
 	MatingResult      pasturePb.MatingResult_Kind     `json:"matingResult"`
 	MatingResultAt    int64                           `json:"matingResultAt"`
 	ExposeEstrusType  pasturePb.ExposeEstrusType_Kind `json:"exposeEstrusType"`
@@ -68,7 +68,7 @@ func NewEventMating2(cow *Cow, req *pasturePb.EventMating) *EventMating {
 		MatingResult:      pasturePb.MatingResult_Invalid,
 		ExposeEstrusType:  pasturePb.ExposeEstrusType_Natural_Estrus,
 		Status:            pasturePb.IsShow_Ok,
-		MatingNumber:      1,
+		MatingTimes:       1,
 		OperationId:       int64(req.OperationId),
 		OperationName:     req.OperationName,
 		FrozenSemenNumber: req.FrozenSemenNumber,

+ 103 - 31
module/backend/analysis_breed.go

@@ -74,6 +74,10 @@ func (s *StoreEntry) SingleFactorInfantSurvivalRateAnalysis(ctx context.Context,
 			spcRate = float32(util.RoundToTwoDecimals((float64(v.PregnantCount) / float64(v.TotalCount)) * 100))
 		}
 		v.SpceRate = spcRate
+		ci95Min, ci95Max := util.ConfidenceInterval2(pregnantRate, float64(v.TotalCount))
+		v.Ci95 = fmt.Sprintf("%d ~ %d", int32(ci95Min), int32(ci95Max))
+		chart.MaxValue = append(chart.MaxValue, int32(ci95Max))
+		chart.MinValue = append(chart.MinValue, int32(ci95Min))
 		totalCountMap[i] = v.TotalCount
 		allTotalCount += v.TotalCount
 		allPregnantRate += pregnantRate
@@ -550,8 +554,41 @@ func (s *StoreEntry) MultipleFactorAnalysis(ctx context.Context, req *pasturePb.
 	}
 
 	pref := s.DB.Model(new(model.EventMating)).
+		Select(`
+			DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m') AS months,  
+			operation_name,frozen_semen_number as bull,lact,mating_times,
+			CASE expose_estrus_type
+				WHEN 1 THEN '脖环揭发'  
+				WHEN 2 THEN '脚环/计步器'  
+				WHEN 3 THEN '自然发情'  
+				WHEN 4 THEN '同期'  
+				ELSE '未知'  
+    		END AS expose_estrus_type,
+			CASE DAYOFWEEK(DATE(FROM_UNIXTIME(reality_day)))
+				WHEN 2 THEN '星期一'  
+				WHEN 3 THEN '星期二'  
+				WHEN 4 THEN '星期三'  
+				WHEN 5 THEN '星期四'  
+				WHEN 6 THEN '星期五'  
+				WHEN 7 THEN '星期六'  
+				WHEN 1 THEN '星期日'  
+				ELSE '未知'  
+			END AS week,
+			COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) AS pregnant_count, -- 怀孕头数  
+			COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) AS empty_pregnant_count, -- 空怀头数  
+			COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) AS abortion_count, -- 流产头数  
+			COUNT(DISTINCT CASE WHEN mating_result IN (?, ?) THEN cow_id END) AS other_count, -- 其他数
+			(  
+				COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) +  
+				COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) +  
+				COUNT(DISTINCT CASE WHEN mating_result IN (?, ?) THEN cow_id END)  
+			) AS total_count -- 总数
+		`, pasturePb.MatingResult_Pregnant, pasturePb.MatingResult_Empty, pasturePb.MatingResult_Abort,
+			pasturePb.MatingResult_Unknown, pasturePb.MatingResult_ReMatch, pasturePb.MatingResult_Pregnant,
+			pasturePb.MatingResult_Empty, pasturePb.MatingResult_Unknown, pasturePb.MatingResult_ReMatch).
 		Where("status = ?", pasturePb.IsShow_Ok).
-		Where("cow_type = ?", req.CowType)
+		Where("cow_type = ?", req.CowType).
+		Where("reality_day BETWEEN ? AND ?", startTimeUnix, endTimeUnix)
 
 	if req.LactCompareSymbol > 0 {
 		switch req.LactCompareSymbol {
@@ -572,40 +609,17 @@ func (s *StoreEntry) MultipleFactorAnalysis(ctx context.Context, req *pasturePb.
 		}
 	}
 
-	if req.MatingTimesCompareSymbol > 0 {
-		switch req.MatingTimesCompareSymbol {
-		case pasturePb.CompareSymbol_Less_Than:
-			pref.Where("mating_number < ?", req.LactStartValue)
-		case pasturePb.CompareSymbol_Less_Than_Or_Equal_To:
-			pref.Where("mating_number <= ?", req.LactStartValue)
-		case pasturePb.CompareSymbol_Greater_Than:
-			pref.Where("mating_number > ?", req.LactStartValue)
-		case pasturePb.CompareSymbol_Greater_Than_Or_Equal_To:
-			pref.Where("mating_number >= ?", req.LactStartValue)
-		case pasturePb.CompareSymbol_Equal_To:
-			pref.Where("mating_number = ?", req.LactStartValue)
-		case pasturePb.CompareSymbol_Not_Equal_To:
-			pref.Where("mating_number != ?", req.LactStartValue)
-		case pasturePb.CompareSymbol_Between:
-			pref.Where("mating_number BETWEEN ? AND ? ", req.LactStartValue, req.LactEndValue)
-		}
-	}
-
-	if req.XAxle > 0 {
-		switch req.XAxle {
-		case pasturePb.MultiFactorAnalysisMethod_Months:
-			pref.Select(`
-				DATE_FORMAT(reality_day, '%Y-%m') AS statistic_method,
-				COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) AS pregnant_count, -- 怀孕头数  
-				COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) AS empty_pregnant_count, -- 空怀头数  
-				COUNT(DISTINCT CASE WHEN mating_result = ? AND mating_result_at > 0 THEN cow_id END) AS abortion_count, -- 流产头数
-			`)
-
+	multiFactorAnalysisMethod := s.MultiFactorAnalysisMethodMap()
+	groupMap := make(map[string]string)
+	for k1, v1 := range multiFactorAnalysisMethod {
+		for k2, v2 := range multiFactorAnalysisMethod {
+			groupMap[fmt.Sprintf("%d%d", k1, k2)] = fmt.Sprintf("%s,%s", v1, v2)
 		}
 	}
 
 	list := make([]*pasturePb.MultiFactorPregnancyRateList, 0)
-	if err := pref.Group("statistic_method").Find(&list).Error; err != nil {
+	if err := pref.Group(groupMap[fmt.Sprintf("%d%d", req.XAxle, req.YAxle)]).
+		Order(fmt.Sprintf("%s", multiFactorAnalysisMethod[req.XAxle])).Find(&list).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
@@ -614,6 +628,64 @@ func (s *StoreEntry) MultipleFactorAnalysis(ctx context.Context, req *pasturePb.
 		PregnantRate: make(map[string]*pasturePb.PregnancyRate),
 	}
 
+	for _, v := range list {
+		switch req.XAxle {
+		case pasturePb.MultiFactorAnalysisMethod_Months:
+			chart.Headers = append(chart.Headers, v.Months)
+			v.StatisticMethod1 = v.Months
+		case pasturePb.MultiFactorAnalysisMethod_Week:
+			chart.Headers = append(chart.Headers, v.Week)
+			v.StatisticMethod1 = v.Week
+		case pasturePb.MultiFactorAnalysisMethod_Operation:
+			chart.Headers = append(chart.Headers, v.OperationName)
+			v.StatisticMethod1 = v.OperationName
+		case pasturePb.MultiFactorAnalysisMethod_Bull:
+			chart.Headers = append(chart.Headers, v.Bull)
+			v.StatisticMethod1 = v.Bull
+		case pasturePb.MultiFactorAnalysisMethod_Lact:
+			chart.Headers = append(chart.Headers, v.Lact)
+			v.StatisticMethod1 = v.Lact
+		case pasturePb.MultiFactorAnalysisMethod_Mating_Times:
+			chart.Headers = append(chart.Headers, v.MatingTimes)
+			v.StatisticMethod1 = v.MatingTimes
+		case pasturePb.MultiFactorAnalysisMethod_Breeding_Method:
+			chart.Headers = append(chart.Headers, v.ExposeEstrusType)
+			v.StatisticMethod1 = v.ExposeEstrusType
+		}
+
+		switch req.YAxle {
+		case pasturePb.MultiFactorAnalysisMethod_Months:
+			v.StatisticMethod2 = v.Months
+		case pasturePb.MultiFactorAnalysisMethod_Week:
+			v.StatisticMethod2 = v.Week
+		case pasturePb.MultiFactorAnalysisMethod_Operation:
+			v.StatisticMethod2 = v.OperationName
+		case pasturePb.MultiFactorAnalysisMethod_Bull:
+			v.StatisticMethod2 = v.Bull
+		case pasturePb.MultiFactorAnalysisMethod_Lact:
+			v.StatisticMethod2 = v.Lact
+		case pasturePb.MultiFactorAnalysisMethod_Mating_Times:
+			v.StatisticMethod2 = v.MatingTimes
+		case pasturePb.MultiFactorAnalysisMethod_Breeding_Method:
+			v.StatisticMethod2 = v.ExposeEstrusType
+		}
+
+		chart.Keys = append(chart.Keys, v.StatisticMethod1)
+		chart.PregnantRate[v.StatisticMethod1] = &pasturePb.PregnancyRate{
+			Params: make(map[string]string),
+		}
+		pregnantRate := float64(0)
+		if v.EmptyPregnantCount+v.PregnantCount > 0 {
+			pregnantRate = float64(v.PregnantCount) / float64(v.EmptyPregnantCount+v.PregnantCount)
+		}
+		v.PregnantRate = float32(util.RoundToTwoDecimals(pregnantRate * 100))
+		spcRate := float32(0)
+		if v.PregnantRate > 0 {
+			spcRate = float32(util.RoundToTwoDecimals((float64(v.PregnantCount) / float64(v.TotalCount)) * 100))
+		}
+		v.SpcRate = spcRate
+	}
+
 	return &pasturePb.MultiFactorPregnancyRateResponse{
 		Code:    http.StatusOK,
 		Message: "ok",

+ 1 - 1
module/backend/calendar.go

@@ -142,7 +142,7 @@ func (s *StoreEntry) ImmunisationCowList(ctx context.Context, dateTime string, p
 	immunizationPlanCowList := make([]*model.CowImmunizationPlan, 0)
 	count := int64(0)
 	if err := s.DB.Model(&model.CowImmunizationPlan{}).
-		Where("plan_start_time <= ?", dateTime).
+		Where("plan_day <= ?", dateTime).
 		Where("status = ?", pasturePb.IsShow_No).
 		Order("id desc").Count(&count).Limit(int(pagination.PageSize)).Offset(int(pagination.PageOffset)).
 		Find(&immunizationPlanCowList).Error; err != nil {

+ 34 - 0
module/backend/config_data_breed.go

@@ -170,3 +170,37 @@ func (s *StoreEntry) LactIntervalSymbolEnumList(isAll string) []*pasturePb.Confi
 	})
 	return configOptions
 }
+
+func (s *StoreEntry) MultiFactorAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Months),
+		Label:    "月份",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Week),
+		Label:    "周",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Operation),
+		Label:    "配种员",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Bull),
+		Label:    "公牛",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Lact),
+		Label:    "胎次",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Mating_Times),
+		Label:    "配次",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.MultiFactorAnalysisMethod_Breeding_Method),
+		Label:    "配种方式",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 12 - 0
module/backend/enum_map.go

@@ -178,3 +178,15 @@ func (s *StoreEntry) DiseaseTypeMap() map[int32]string {
 	}
 	return res
 }
+
+func (s *StoreEntry) MultiFactorAnalysisMethodMap() map[pasturePb.MultiFactorAnalysisMethod_Kind]string {
+	res := make(map[pasturePb.MultiFactorAnalysisMethod_Kind]string)
+	res[pasturePb.MultiFactorAnalysisMethod_Months] = "months"
+	res[pasturePb.MultiFactorAnalysisMethod_Week] = "week"
+	res[pasturePb.MultiFactorAnalysisMethod_Operation] = "operation_name"
+	res[pasturePb.MultiFactorAnalysisMethod_Bull] = "frozen_semen_number"
+	res[pasturePb.MultiFactorAnalysisMethod_Lact] = "lact"
+	res[pasturePb.MultiFactorAnalysisMethod_Mating_Times] = "mating_times"
+	res[pasturePb.MultiFactorAnalysisMethod_Breeding_Method] = "expose_estrus_type"
+	return res
+}

+ 1 - 0
module/backend/enum_options.go

@@ -187,6 +187,7 @@ func (s *StoreEntry) SystemBaseConfigOptions(ctx context.Context, optionsName, i
 		"diseaseAnalysisMethod":      s.DiseaseAnalysisMethodEnumList,
 		"singleFactorAnalysisMethod": s.SingleFactorAnalysisMethodEnumList,
 		"lactIntervalSymbol":         s.LactIntervalSymbolEnumList,
+		"multiFactorAnalysisMethod":  s.MultiFactorAnalysisMethodEnumList,
 	}
 
 	getConfigFunc, ok := getConfigFuncMap[optionsName]

+ 2 - 0
module/backend/event_breed.go

@@ -453,6 +453,8 @@ func (s *StoreEntry) MatingCreate(ctx context.Context, req *pasturePb.EventMatin
 				).Error; err != nil {
 				return xerr.WithStack(err)
 			}
+
+			// todo 更新每头牛只的胎次配次
 		}
 		return nil
 	}); err != nil {

+ 40 - 0
util/util.go

@@ -213,3 +213,43 @@ func GetRangeDayByDays(startDay, endDay string, days int32) ([][]string, error)
 	}
 	return res, nil
 }
+
+// 计算样本均值
+func mean(data []float64) float64 {
+	sum := 0.0
+	for _, v := range data {
+		sum += v
+	}
+	return sum / float64(len(data))
+}
+
+// 计算样本标准差
+func stddev(data []float64, mean float64) float64 {
+	sum := 0.0
+	for _, v := range data {
+		sum += (v - mean) * (v - mean)
+	}
+	variance := sum / float64(len(data)-1)
+	return math.Sqrt(variance)
+}
+
+// ConfidenceInterval 计算95%置信区间
+func ConfidenceInterval(data []float64) (float64, float64) {
+	n := float64(len(data))
+	meanVal := mean(data)
+	stdDev := stddev(data, meanVal)
+	z := 1.96 // 95%置信水平对应的Z值
+
+	marginOfError := z * (stdDev / math.Sqrt(n))
+	lowerBound := meanVal - marginOfError
+	upperBound := meanVal + marginOfError
+
+	return lowerBound, upperBound
+}
+
+// ConfidenceInterval2 计算95%置信区间
+func ConfidenceInterval2(p float64, total float64) (min, max float64) {
+	z := 1.96 // 95%置信水平对应的Z值
+	marginOf := z * math.Sqrt(p*(1-p)/total) * 100
+	return math.Max(0, p*100-math.Ceil(marginOf)), math.Max(1, p*100+math.Ceil(marginOf))
+}

+ 34 - 0
util/util_test.go

@@ -309,3 +309,37 @@ func TestGetRangeDayByDays(t *testing.T) {
 		})
 	}
 }
+
+func TestConfidenceInterval2(t *testing.T) {
+	tests := []struct {
+		p     float64
+		total float64
+		min   float64
+		max   float64
+	}{
+		{
+			p:     0.38,
+			total: 114,
+			min:   29,
+			max:   47,
+		},
+		{
+			p:     0.49,
+			total: 142,
+			min:   40,
+			max:   58,
+		},
+		{
+			p:     0.41,
+			total: 125,
+			min:   32,
+			max:   50,
+		},
+	}
+
+	for _, tt := range tests {
+		min, max := ConfidenceInterval2(tt.p, tt.total)
+		assert.Equal(t, tt.min, min)
+		assert.Equal(t, tt.max, max)
+	}
+}