Forráskód Böngészése

analysis:单因素受胎率分析之按月份分析

Yi 5 hónapja
szülő
commit
0c2ecdb031

+ 2 - 1
README.md

@@ -40,4 +40,5 @@ todo列表:
 - [ ] 前后端部署架构【k8s,docker-compose,docker-swarm】namespace隔离,需要考虑的问题【1.一次性任务,2. 定时任务 3. 数据收集,4. 日志收集 5. 报警介入】
 - [ ] 所有事件录入梳理【批量录入,excel导入,信息人员与操作人员统一规范】
 - [ ] 药品优化成药品名称关联生产商
-- [ ] 框架logrus日志优化【未按照指定天数的日志自动删除,待验证】
+- [ ] 框架logrus日志优化【未按照指定天数的日志自动删除,待验证】
+- [ ] 犊牛的牛只品种是根据母牛的品种来确定,还是根据公牛来确定?【目前是根据母牛品种来确定】

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20241029090643-f4a03baaf55f
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20241030093930-85784b846ab4
 	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

+ 10 - 0
go.sum

@@ -38,6 +38,14 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241029090643-f4a03baaf55f h1:TUKiPo/cMn3pbGcPXGs1UOjc7Ut8B8flRZn5XGW4A7Q=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20241029090643-f4a03baaf55f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030025349-c1c626237949 h1:rQHN32xKgIVeWTNjrDvadRSdWDF39b+ofx0VQyFFO8o=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030025349-c1c626237949/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030054226-4e23887c505f h1:e+yijGs8inTX+zmjAag5ci2UZbnXh/M2aXO8F12X+sM=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030054226-4e23887c505f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030072744-bd563f5efd21 h1:lKRO0G5QbIoNxN2pXajX6VKCTXolE/1G50m/TAeaoL4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030072744-bd563f5efd21/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030093930-85784b846ab4 h1:7GYTs67byos3e1BIdGMjJM8qlWNpho5LQovOCcrNVxo=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20241030093930-85784b846ab4/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=
@@ -170,6 +178,7 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
@@ -601,6 +610,7 @@ go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM=
 go.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
 go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=

+ 26 - 0
http/handler/analysis/analysis.go

@@ -224,3 +224,29 @@ func DiseaseCureReport(c *gin.Context) {
 	}
 	ginutil.JSONResp(c, res)
 }
+
+func SingleFactorInfantSurvivalRate(c *gin.Context) {
+	var req pasturePb.SingleFactorPregnancyRateRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.StartDayTime, valid.Required),
+		valid.Field(&req.EndDayTime, valid.Required),
+		valid.Field(&req.AnalysisMethod, valid.Required),
+		valid.Field(&req.CowType, valid.Required),
+		valid.Field(&req.LactInterval, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.SingleFactorInfantSurvivalRateAnalysis(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}

+ 1 - 0
http/route/analysis_api.go

@@ -22,5 +22,6 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		pastureRoute.POST("/pregnancy/report", analysis.PregnancyReport)
 		pastureRoute.POST("/calving/report", analysis.CalvingReport)
 		pastureRoute.POST("/disease/cure/report", analysis.DiseaseCureReport)
+		pastureRoute.POST("/single/factor/pregnant/report", analysis.SingleFactorInfantSurvivalRate)
 	}
 }

+ 2 - 0
model/event_mating.go

@@ -12,6 +12,7 @@ type EventMating struct {
 	DayAge            int64                           `json:"dayAge"`
 	Lact              int8                            `json:"lact"`
 	CowType           pasturePb.CowType_Kind          `json:"cowType"`
+	CowKind           pasturePb.CowKind_Kind          `json:"cowKind"`
 	CalvingAge        int32                           `json:"calvingAge"`
 	PlanDay           int64                           `json:"planDay"`
 	EndDay            int64                           `json:"endDay"`
@@ -40,6 +41,7 @@ func NewEventMating(cow *Cow, planDay int64) *EventMating {
 		CowId:            cow.Id,
 		Lact:             int8(cow.Lact),
 		CowType:          cow.CowType,
+		CowKind:          cow.CowKind,
 		CalvingAt:        cow.CalvingAt,
 		PlanDay:          planDay,
 		EndDay:           planDay,

+ 161 - 0
module/backend/analysis_breed.go

@@ -0,0 +1,161 @@
+package backend
+
+import (
+	"context"
+	"kpt-pasture/model"
+	"kpt-pasture/util"
+	"net/http"
+
+	"gitee.com/xuyiping_admin/pkg/xerr"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
+
+// SingleFactorInfantSurvivalRateAnalysis 单因素受胎率分析
+func (s *StoreEntry) SingleFactorInfantSurvivalRateAnalysis(ctx context.Context, req *pasturePb.SingleFactorPregnancyRateRequest) (*pasturePb.SingleFactorPregnancyRateResponse, error) {
+	startTimeUnix := util.TimeParseLocalUnix(req.StartDayTime)
+	endTimeUnix := util.TimeParseLocalEndUnix(req.EndDayTime)
+	if startTimeUnix == 0 || endTimeUnix == 0 || endTimeUnix <= startTimeUnix {
+		return nil, xerr.Custom("开始时间不能大于结束时间")
+	}
+	var err error
+	list := make([]*pasturePb.SingleFactorPregnancyRateList, 0)
+	chart := &pasturePb.SingleFactorPregnancyRateChart{
+		Headers:             make([]string, 0),
+		PregnantRate:        make([]float32, 0),
+		MaxValue:            make([]int32, 0),
+		MinValue:            make([]int32, 0),
+		AveragePregnantRate: 0,
+	}
+	switch req.AnalysisMethod {
+	case pasturePb.SingleFactorAnalysisMethod_Cycle:
+		list, err = s.SingleFactorAnalysisMethodCycle(ctx, req)
+	case pasturePb.SingleFactorAnalysisMethod_Months:
+		list, err = s.SingleFactorAnalysisMethodMonths(ctx, req)
+	case pasturePb.SingleFactorAnalysisMethod_Mating_Times:
+	case pasturePb.SingleFactorAnalysisMethod_Breeding_Method:
+	case pasturePb.SingleFactorAnalysisMethod_Breeding_Company:
+	case pasturePb.SingleFactorAnalysisMethod_Operation:
+	case pasturePb.SingleFactorAnalysisMethod_Mating_Interval:
+	case pasturePb.SingleFactorAnalysisMethod_Bull:
+	case pasturePb.SingleFactorAnalysisMethod_Breeding_Cycle:
+	case pasturePb.SingleFactorAnalysisMethod_Week:
+	case pasturePb.SingleFactorAnalysisMethod_Lact:
+	default:
+		return nil, xerr.Custom("错误的统计方式")
+	}
+
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	totalCountMap := make(map[int]int32, len(list))
+	allTotalCount := int32(0)     // 总头数
+	allPregnantRate := float64(0) // 总受胎率
+	for i, v := range list {
+		chart.Headers = append(chart.Headers, v.StatisticMethod)
+		pregnantRate := float64(0)
+		if v.EmptyPregnantCount+v.PregnantCount > 0 {
+			pregnantRate = float64(v.PregnantCount) / float64(v.EmptyPregnantCount+v.PregnantCount)
+		}
+		chart.PregnantRate = append(chart.PregnantRate, float32(util.RoundToTwoDecimals(pregnantRate*100)))
+		v.TotalCount = v.PregnantCount + v.EmptyPregnantCount + v.OtherCount
+		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.SpceRate = spcRate
+		totalCountMap[i] = v.TotalCount
+		allTotalCount += v.TotalCount
+		allPregnantRate += pregnantRate
+	}
+	for i, v := range totalCountMap {
+		if allTotalCount <= 0 || list[i].TotalCount <= 0 {
+			continue
+		}
+		list[i].TotalRate = float32(util.RoundToTwoDecimals(float64(v) / float64(allTotalCount) * 100))
+	}
+
+	chart.AveragePregnantRate = float32(util.RoundToTwoDecimals(allPregnantRate/float64(len(list))) * 100)
+
+	return &pasturePb.SingleFactorPregnancyRateResponse{
+		Code:    http.StatusOK,
+		Message: "ok",
+		Data: &pasturePb.SingleFactorPregnancyRateData{
+			Total: int32(len(list)),
+			List:  list,
+			Chart: chart,
+		},
+	}, nil
+}
+
+func (s *StoreEntry) SingleFactorAnalysisMethodCycle(ctx context.Context, req *pasturePb.SingleFactorPregnancyRateRequest) ([]*pasturePb.SingleFactorPregnancyRateList, error) {
+	/*dateTimeRange, err := util.GetRangeDayByDays(req.StartDayTime, req.EndDayTime, req.Value)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}*/
+	res := make([]*pasturePb.SingleFactorPregnancyRateList, 0)
+
+	return res, nil
+}
+
+func (s *StoreEntry) SingleFactorAnalysisMethodMonths(ctx context.Context, req *pasturePb.SingleFactorPregnancyRateRequest) ([]*pasturePb.SingleFactorPregnancyRateList, error) {
+	startDayTimeUnix := util.TimeParseLocalUnix(req.StartDayTime)
+	endDayTimeUnix := util.TimeParseLocalEndUnix(req.EndDayTime)
+	if startDayTimeUnix == 0 || endDayTimeUnix == 0 || endDayTimeUnix <= startDayTimeUnix {
+		return nil, xerr.Custom("开始时间不能大于结束时间")
+	}
+
+	res := make([]*pasturePb.SingleFactorPregnancyRateList, 0)
+	pref := s.DB.Model(new(model.EventMating)).
+		Select(`
+		DATE_FORMAT(FROM_UNIXTIME(reality_day), '%Y-%m') AS months,
+		DATE_FORMAT(FROM_UNIXTIME(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, -- 流产头数  
+		COUNT(DISTINCT CASE WHEN mating_result IN (?, ?) THEN cow_id END) AS other_count, -- 其他数
+		(  
+        	COUNT(DISTINCT CASE WHEN mating_result = 3 AND mating_result_at > 0 THEN cow_id END) +  
+        	COUNT(DISTINCT CASE WHEN mating_result = 4 AND mating_result_at > 0 THEN cow_id END) +  
+        	COUNT(DISTINCT CASE WHEN mating_result IN (1, 2) THEN cow_id END)  
+		) AS total_count -- 总数   
+		`, pasturePb.MatingResult_Pregnant, pasturePb.MatingResult_Empty, pasturePb.MatingResult_Abort,
+			pasturePb.MatingResult_Unknown, pasturePb.MatingResult_ReMatch).
+		Where("status = ?", pasturePb.IsShow_Ok).
+		Where("reality_day BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix).
+		Where("reality_day > 0")
+	if req.CowType > 0 {
+		pref.Where("cow_type = ?", req.CowType)
+	}
+
+	if req.CowKind > 0 {
+		pref.Where("cow_kind = ?", req.CowKind)
+	}
+
+	if req.LactInterval > 0 {
+		switch req.LactInterval {
+		case pasturePb.LactIntervalSymbol_Less_Than:
+			pref.Where("lact < ?", req.LactIntervalStartValue)
+		case pasturePb.LactIntervalSymbol_Less_Than_Or_Equal_To:
+			pref.Where("lact <= ?", req.LactIntervalStartValue)
+		case pasturePb.LactIntervalSymbol_Greater_Than:
+			pref.Where("lact > ?", req.LactIntervalStartValue)
+		case pasturePb.LactIntervalSymbol_Greater_Than_Or_Equal_To:
+			pref.Where("lact >= ?", req.LactIntervalStartValue)
+		case pasturePb.LactIntervalSymbol_Equal_To:
+			pref.Where("lact = ?", req.LactIntervalStartValue)
+		case pasturePb.LactIntervalSymbol_Not_Equal_To:
+			pref.Where("lact != ?", req.LactIntervalStartValue)
+		case pasturePb.LactIntervalSymbol_Between:
+			pref.Where("lact BETWEEN ? AND ? ", req.LactIntervalStartValue, req.LactIntervalEndValue)
+		default:
+			return nil, xerr.Custom("错误的胎次区间符号")
+		}
+	}
+
+	if err := pref.Group("months").Order("months").Find(&res).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return res, nil
+}

+ 127 - 81
module/backend/analysis_other.go

@@ -7,6 +7,8 @@ import (
 	"kpt-pasture/util"
 	"net/http"
 
+	"gorm.io/gorm"
+
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 	"gitee.com/xuyiping_admin/pkg/xerr"
 )
@@ -165,6 +167,7 @@ func (s *StoreEntry) CalvingReport(ctx context.Context, req *pasturePb.CalvingRe
 	}, nil
 }
 
+// DiseaseCureReport 疾病治愈率报告
 func (s *StoreEntry) DiseaseCureReport(ctx context.Context, req *pasturePb.DiseaseCureRateRequest) (*pasturePb.DiseaseCureRateResponse, error) {
 	lastDayOfMonth, err := util.GetLastDayOfMonth(req.EndDayTime)
 	if err != nil {
@@ -179,56 +182,14 @@ func (s *StoreEntry) DiseaseCureReport(ctx context.Context, req *pasturePb.Disea
 
 	diseaseCureRateList1 := make([]*pasturePb.DiseaseCureRateList, 0)
 	diseaseCureRateList2 := make([]*pasturePb.DiseaseCureRateList, 0)
-	pref1 := s.DB.Model(new(model.EventCowDisease)).
-		Select(
-			fmt.Sprint(`DATE_FORMAT(FROM_UNIXTIME(diagnosed_at), '%Y-%m') AS months,`)+
-				fmt.Sprintf(`
-				diagnose_id as disease_id,
-				diagnose_name as disease_name,
-				diagnose_operation_id as operation_id,
-				diagnose_operation_name as operation_name,
-				disease_type,
-				COUNT(DISTINCT CASE WHEN health_status = %d THEN cow_id END) AS disease_treatment_count, -- 治疗头数  
-				COUNT(DISTINCT CASE WHEN health_status = %d AND curable_at > 0 THEN cow_id END) AS disease_cure_count, -- 治愈头数  
-				COUNT(DISTINCT CASE WHEN diagnosed_result = %d THEN cow_id END) AS disease_count, -- 发病数  
-				COUNT(DISTINCT CASE WHEN health_status IN (%d, %d) THEN cow_id END) AS die_out_count, -- 死淘数  
-				COUNT(DISTINCT CASE   
-						   WHEN health_status = %d  
-						   AND TIMESTAMPDIFF(DAY, FROM_UNIXTIME(disease_at), FROM_UNIXTIME(curable_at)) <= 7   
-						   THEN cow_id   
-					   END) AS seven_cure_count, -- 7天内治愈头数  
-				COUNT(DISTINCT CASE   
-						   WHEN health_status = %d  
-						   AND TIMESTAMPDIFF(DAY, FROM_UNIXTIME(disease_at), FROM_UNIXTIME(curable_at)) <= 14   
-						   THEN cow_id   
-					   END) AS seventeen_cure_count, -- 14天内治愈头数 
-				COUNT(DISTINCT CASE   
-						   WHEN health_status = %d  
-						   AND TIMESTAMPDIFF(DAY, FROM_UNIXTIME(disease_at), FROM_UNIXTIME(curable_at)) <= 30  
-						   THEN cow_id   
-					   END) AS thirty_cure_count -- 30天内治愈头数         
-				`, pasturePb.HealthStatus_Treatment, pasturePb.HealthStatus_Curable, pasturePb.IsShow_Ok, pasturePb.HealthStatus_Out,
-					pasturePb.HealthStatus_Dead, pasturePb.HealthStatus_Curable, pasturePb.HealthStatus_Curable, pasturePb.HealthStatus_Curable,
-				)).
-		Where("diagnosed_result = ?", pasturePb.IsShow_Ok).
-		Where("diagnosed_at BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix)
-	if req.CowType > 0 {
-		pref1.Where("cow_type = ?", req.CowType)
+	pref1, err := s.query1(ctx, req.CowType, startDayTimeUnix, endDayTimeUnix)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	pref2, err := s.query2(ctx, startDayTimeUnix, endDayTimeUnix)
+	if err != nil {
+		return nil, xerr.WithStack(err)
 	}
-
-	pref2 := s.DB.Model(new(model.EventCowTreatment)).
-		Select(`
-		DATE_FORMAT(FROM_UNIXTIME(treatment_at), '%Y-%m') AS months,
-		disease_id,
-		disease_name,
-		disease_type,
-		prescription_id,
-		prescription_name,
-		operation_id,
-		operation_name,
-		COUNT(DISTINCT cow_id) AS disease_treatment_count`,
-		).Where("treatment_at BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix)
-
 	switch req.AnalysisMethod {
 	case pasturePb.DiseaseAnalysisMethod_Months:
 		pref1.Where("diagnosed_at > 0").Group("months").Order("months")
@@ -243,7 +204,7 @@ func (s *StoreEntry) DiseaseCureReport(ctx context.Context, req *pasturePb.Disea
 		pref1.Where("diagnose_operation_id > 0").Group("diagnose_operation_id").Order("diagnose_operation_id")
 		pref2.Where("operation_id > 0").Group("operation_id").Order("operation_id")
 	default:
-		return nil, xerr.Custom("请选择统计方式")
+		return nil, xerr.Custom("错误的统计方式")
 	}
 
 	if err = pref1.Find(&diseaseCureRateList1).Error; err != nil {
@@ -254,37 +215,8 @@ func (s *StoreEntry) DiseaseCureReport(ctx context.Context, req *pasturePb.Disea
 		return nil, xerr.WithStack(err)
 	}
 
-	for _, v1 := range diseaseCureRateList1 {
-		for _, v2 := range diseaseCureRateList2 {
-			switch req.AnalysisMethod {
-			case pasturePb.DiseaseAnalysisMethod_Months:
-				if v1.Months != v2.Months {
-					continue
-				}
-				v1.DiseaseTreatmentCount = v2.DiseaseTreatmentCount
-			case pasturePb.DiseaseAnalysisMethod_Disease_Category:
-				if v1.DiseaseType != v2.DiseaseType {
-					continue
-				}
-				v1.DiseaseTreatmentCount = v2.DiseaseTreatmentCount
-			case pasturePb.DiseaseAnalysisMethod_Disease:
-				if v1.DiseaseId != v2.DiseaseId {
-					continue
-				}
-				v1.DiseaseTreatmentCount = v2.DiseaseTreatmentCount
-			case pasturePb.DiseaseAnalysisMethod_Operator:
-				if v1.OperationId != v2.OperationId {
-					continue
-				}
-				v1.DiseaseTreatmentCount = v2.DiseaseTreatmentCount
-			case pasturePb.DiseaseAnalysisMethod_Prescription:
-				if v1.PrescriptionId != v2.PrescriptionId {
-					continue
-				}
-				v1.DiseaseTreatmentCount = v2.DiseaseTreatmentCount
-			}
-		}
-	}
+	// 处理数据
+	processDiseaseCureRateList(diseaseCureRateList1, diseaseCureRateList2, req.AnalysisMethod)
 	chart := &pasturePb.DiseaseCureRateChart{
 		Headers:          make([]string, 0),
 		DiseaseCureCount: make([]int32, 0),
@@ -326,3 +258,117 @@ func (s *StoreEntry) DiseaseCureReport(ctx context.Context, req *pasturePb.Disea
 		},
 	}, nil
 }
+
+func (s *StoreEntry) query1(ctx context.Context, cowType pasturePb.CowType_Kind, startDayTimeUnix, endDayTimeUnix int64) (*gorm.DB, error) {
+	pref := s.DB.Model(new(model.EventCowDisease)).
+		Select(
+			`DATE_FORMAT(FROM_UNIXTIME(diagnosed_at), '%Y-%m') AS months,
+				diagnose_id as disease_id,
+				diagnose_name as disease_name,
+				diagnose_operation_id as operation_id,
+				diagnose_operation_name as operation_name,
+				disease_type,
+				COUNT(DISTINCT CASE WHEN health_status = ? THEN cow_id END) AS disease_treatment_count, -- 治疗头数  
+				COUNT(DISTINCT CASE WHEN health_status = ? AND curable_at > 0 THEN cow_id END) AS disease_cure_count, -- 治愈头数  
+				COUNT(DISTINCT CASE WHEN diagnosed_result = ? THEN cow_id END) AS disease_count, -- 发病数  
+				COUNT(DISTINCT CASE WHEN health_status IN (?, ?) THEN cow_id END) AS die_out_count, -- 死淘数  
+				COUNT(DISTINCT CASE   
+						   WHEN health_status = ?  
+						   AND TIMESTAMPDIFF(DAY, FROM_UNIXTIME(disease_at), FROM_UNIXTIME(curable_at)) <= 7   
+						   THEN cow_id   
+					   END) AS seven_cure_count, -- 7天内治愈头数  
+				COUNT(DISTINCT CASE   
+						   WHEN health_status = ?  
+						   AND TIMESTAMPDIFF(DAY, FROM_UNIXTIME(disease_at), FROM_UNIXTIME(curable_at)) <= 14   
+						   THEN cow_id   
+					   END) AS seventeen_cure_count, -- 14天内治愈头数 
+				COUNT(DISTINCT CASE   
+						   WHEN health_status = ?  
+						   AND TIMESTAMPDIFF(DAY, FROM_UNIXTIME(disease_at), FROM_UNIXTIME(curable_at)) <= 30  
+						   THEN cow_id   
+					   END) AS thirty_cure_count -- 30天内治愈头数
+				`,
+			pasturePb.HealthStatus_Treatment, pasturePb.HealthStatus_Curable, pasturePb.IsShow_Ok, pasturePb.HealthStatus_Out,
+			pasturePb.HealthStatus_Dead, pasturePb.HealthStatus_Curable, pasturePb.HealthStatus_Curable, pasturePb.HealthStatus_Curable,
+		).
+		Where("diagnosed_result = ?", pasturePb.IsShow_Ok).
+		Where("diagnosed_at BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix)
+	if cowType > 0 {
+		pref.Where("cow_type = ?", cowType)
+	}
+
+	return pref, nil
+}
+
+func (s *StoreEntry) query2(ctx context.Context, startDayTimeUnix, endDayTimeUnix int64) (*gorm.DB, error) {
+	pref := s.DB.Model(new(model.EventCowTreatment)).
+		Select(`
+		DATE_FORMAT(FROM_UNIXTIME(treatment_at), '%Y-%m') AS months,
+		disease_id,
+		disease_name,
+		disease_type,
+		prescription_id,
+		prescription_name,
+		operation_id,
+		operation_name,
+		COUNT(DISTINCT cow_id) AS disease_treatment_count`,
+		).Where("treatment_at BETWEEN ? AND ?", startDayTimeUnix, endDayTimeUnix)
+	return pref, nil
+}
+
+// 构建哈希表
+func buildMap(list []*pasturePb.DiseaseCureRateList, method pasturePb.DiseaseAnalysisMethod_Kind) map[interface{}]*pasturePb.DiseaseCureRateList {
+	m := make(map[interface{}]*pasturePb.DiseaseCureRateList)
+	for _, v := range list {
+		var key interface{}
+		switch method {
+		case pasturePb.DiseaseAnalysisMethod_Months:
+			key = v.Months
+		case pasturePb.DiseaseAnalysisMethod_Disease_Category:
+			key = v.DiseaseType
+		case pasturePb.DiseaseAnalysisMethod_Disease:
+			key = v.DiseaseId
+		case pasturePb.DiseaseAnalysisMethod_Operator:
+			key = v.OperationId
+		case pasturePb.DiseaseAnalysisMethod_Prescription:
+			key = v.PrescriptionId
+		}
+		m[key] = v
+	}
+	return m
+}
+
+// 更新 DiseaseTreatmentCount
+func updateDiseaseTreatmentCount(v1 *pasturePb.DiseaseCureRateList, v2 *pasturePb.DiseaseCureRateList) {
+	v1.DiseaseTreatmentCount = v2.DiseaseTreatmentCount
+}
+
+// 主逻辑
+func processDiseaseCureRateList(diseaseCureRateList1, diseaseCureRateList2 []*pasturePb.DiseaseCureRateList, analysisMethod pasturePb.DiseaseAnalysisMethod_Kind) {
+	if len(diseaseCureRateList1) == 0 || len(diseaseCureRateList2) == 0 {
+		return
+	}
+
+	map2 := buildMap(diseaseCureRateList2, analysisMethod)
+	for i := range diseaseCureRateList1 {
+		v1 := diseaseCureRateList1[i]
+		var key interface{}
+		switch analysisMethod {
+		case pasturePb.DiseaseAnalysisMethod_Months:
+			key = v1.Months
+		case pasturePb.DiseaseAnalysisMethod_Disease_Category:
+			key = v1.DiseaseType
+		case pasturePb.DiseaseAnalysisMethod_Disease:
+			key = v1.DiseaseId
+		case pasturePb.DiseaseAnalysisMethod_Operator:
+			key = v1.OperationId
+		case pasturePb.DiseaseAnalysisMethod_Prescription:
+			key = v1.PrescriptionId
+		default:
+			continue // 处理无效的 AnalysisMethod
+		}
+		if v2, ok := map2[key]; ok {
+			updateDiseaseTreatmentCount(v1, v2)
+		}
+	}
+}

+ 172 - 0
module/backend/config_data_breed.go

@@ -0,0 +1,172 @@
+package backend
+
+import (
+	"kpt-pasture/model"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
+	"go.uber.org/zap"
+)
+
+func (s *StoreEntry) LactEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+			Value:    int32(0),
+			Label:    "全部",
+			Disabled: true,
+		})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(1),
+		Label:    "1",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(2),
+		Label:    "2",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(3),
+		Label:    "3",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(4),
+		Label:    ">3",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) DiseaseAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	if isAll == model.IsAllYes {
+		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+			Value:    int32(pasturePb.DiseaseAnalysisMethod_Invalid),
+			Label:    "全部",
+			Disabled: true,
+		})
+	}
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DiseaseAnalysisMethod_Months),
+		Label:    "按月份",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DiseaseAnalysisMethod_Disease_Category),
+		Label:    "疾病分类",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DiseaseAnalysisMethod_Disease),
+		Label:    "疾病名称",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DiseaseAnalysisMethod_Operator),
+		Label:    "兽医",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.DiseaseAnalysisMethod_Prescription),
+		Label:    "处方",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) diseaseTypeEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configDiseaseTypeList := make([]*model.ConfigDiseaseType, 0)
+	configOptionsList := make([]*pasturePb.ConfigOptionsList, 0)
+	if err := s.DB.Where("is_show = ?", pasturePb.IsShow_Ok).Find(&configDiseaseTypeList).Error; err != nil {
+		zaplog.Error("diseaseTypeEnumList", zap.Any("Find", err))
+	}
+
+	for _, v := range configDiseaseTypeList {
+		configOptionsList = append(configOptionsList, &pasturePb.ConfigOptionsList{
+			Value:    int32(v.Id),
+			Label:    v.Name,
+			Disabled: true,
+		})
+	}
+	return configOptionsList
+}
+
+func (s *StoreEntry) SingleFactorAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Cycle),
+		Label:    "按周期分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Months),
+		Label:    "按月份分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Mating_Times),
+		Label:    "按配种次数分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Breeding_Method),
+		Label:    "按配种方式分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Breeding_Company),
+		Label:    "按育种公司分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Operation),
+		Label:    "按兽医人员分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Mating_Interval),
+		Label:    "按配种间隔分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Bull),
+		Label:    "按公牛号分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Breeding_Cycle),
+		Label:    "按配种周期分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Week),
+		Label:    "按星期分析",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.SingleFactorAnalysisMethod_Lact),
+		Label:    "按胎次分析",
+		Disabled: true,
+	})
+	return configOptions
+}
+
+func (s *StoreEntry) LactIntervalSymbolEnumList(isAll string) []*pasturePb.ConfigOptionsList {
+	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
+	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Less_Than),
+		Label:    "<",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Less_Than_Or_Equal_To),
+		Label:    "<=",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Greater_Than),
+		Label:    ">",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Greater_Than_Or_Equal_To),
+		Label:    ">=",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Equal_To),
+		Label:    "=",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Not_Equal_To),
+		Label:    "!=",
+		Disabled: true,
+	}, &pasturePb.ConfigOptionsList{
+		Value:    int32(pasturePb.LactIntervalSymbol_Between),
+		Label:    "区间",
+		Disabled: true,
+	})
+	return configOptions
+}

+ 0 - 79
module/backend/config_data_other.go

@@ -536,82 +536,3 @@ func (s *StoreEntry) CalvingAnalysisMethodEnumList(isAll string) []*pasturePb.Co
 	})
 	return configOptions
 }
-
-func (s *StoreEntry) LactEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	if isAll == model.IsAllYes {
-		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-			Value:    int32(0),
-			Label:    "全部",
-			Disabled: true,
-		})
-	}
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(1),
-		Label:    "1",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(2),
-		Label:    "2",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(3),
-		Label:    "3",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(4),
-		Label:    ">3",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func (s *StoreEntry) DiseaseAnalysisMethodEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configOptions := make([]*pasturePb.ConfigOptionsList, 0)
-	if isAll == model.IsAllYes {
-		configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-			Value:    int32(pasturePb.DiseaseAnalysisMethod_Invalid),
-			Label:    "全部",
-			Disabled: true,
-		})
-	}
-	configOptions = append(configOptions, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.DiseaseAnalysisMethod_Months),
-		Label:    "按月份",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.DiseaseAnalysisMethod_Disease_Category),
-		Label:    "疾病分类",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.DiseaseAnalysisMethod_Disease),
-		Label:    "疾病名称",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.DiseaseAnalysisMethod_Operator),
-		Label:    "兽医",
-		Disabled: true,
-	}, &pasturePb.ConfigOptionsList{
-		Value:    int32(pasturePb.DiseaseAnalysisMethod_Prescription),
-		Label:    "处方",
-		Disabled: true,
-	})
-	return configOptions
-}
-
-func (s *StoreEntry) diseaseTypeEnumList(isAll string) []*pasturePb.ConfigOptionsList {
-	configDiseaseTypeList := make([]*model.ConfigDiseaseType, 0)
-	configOptionsList := make([]*pasturePb.ConfigOptionsList, 0)
-	if err := s.DB.Where("is_show = ?", pasturePb.IsShow_Ok).Find(&configDiseaseTypeList).Error; err != nil {
-		zaplog.Error("diseaseTypeEnumList", zap.Any("Find", err))
-	}
-
-	for _, v := range configDiseaseTypeList {
-		configOptionsList = append(configOptionsList, &pasturePb.ConfigOptionsList{
-			Value:    int32(v.Id),
-			Label:    v.Name,
-			Disabled: true,
-		})
-	}
-	return configOptionsList
-}

+ 28 - 27
module/backend/enum_options.go

@@ -158,34 +158,35 @@ func (s *StoreEntry) SystemBaseConfigOptions(ctx context.Context, optionsName, i
 	if optionsName == "" {
 		return nil, xerr.New("optionsName is empty")
 	}
-
 	getConfigFuncMap := map[string]func(isAll string) []*pasturePb.ConfigOptionsList{
-		"childNumber":            s.ChildNumberEnumList,
-		"calvingLevel":           s.CalvingLevelEnumList,
-		"dystociaReason":         s.DystociaReasonEnumList,
-		"drugCategory":           s.DrugCategoryEnumList,
-		"drugUsage":              s.DrugUsageEnumList,
-		"unit":                   s.UnitEnumList,
-		"pregnantCheckResult":    s.PregnantCheckResultEnumList,
-		"pregnantCheckMethod":    s.PregnantCheckMethodEnumList,
-		"exposeEstrusType":       s.ExposeEstrusTypeEnumList,
-		"frozenSemenType":        s.FrozenSemenTypeEnumList,
-		"week":                   s.WeekEnumList,
-		"month":                  s.MonthEnumList,
-		"sameTimeCowType":        s.SameTimeCowTypeEnumList,
-		"sameTimeType":           s.SameTimeTypeEnumList,
-		"immunizationCowType":    s.ImmunizationCowTypeEnumList,
-		"workOrderFrequency":     s.WorkOrderFrequencyEnumList,
-		"workOrderSubUnit":       s.WorkOrderSubUnitEnumList,
-		"workOrderPriority":      s.WorkOrderPriorityEnumList,
-		"workOrderCategory":      s.WorkOrderCategoryEnumList,
-		"immunizationConditions": s.ImmunizationConditionsEnumList,
-		"abortionReasons":        s.AbortionReasonsEnumList,
-		"healthStatus":           s.HealthStatusEnumList,
-		"calendarType":           CalendarTypeEnumList,
-		"calvingAnalysisMethod":  s.CalvingAnalysisMethodEnumList,
-		"lact":                   s.LactEnumList,
-		"diseaseAnalysisMethod":  s.DiseaseAnalysisMethodEnumList,
+		"childNumber":                s.ChildNumberEnumList,
+		"calvingLevel":               s.CalvingLevelEnumList,
+		"dystociaReason":             s.DystociaReasonEnumList,
+		"drugCategory":               s.DrugCategoryEnumList,
+		"drugUsage":                  s.DrugUsageEnumList,
+		"unit":                       s.UnitEnumList,
+		"pregnantCheckResult":        s.PregnantCheckResultEnumList,
+		"pregnantCheckMethod":        s.PregnantCheckMethodEnumList,
+		"exposeEstrusType":           s.ExposeEstrusTypeEnumList,
+		"frozenSemenType":            s.FrozenSemenTypeEnumList,
+		"week":                       s.WeekEnumList,
+		"month":                      s.MonthEnumList,
+		"sameTimeCowType":            s.SameTimeCowTypeEnumList,
+		"sameTimeType":               s.SameTimeTypeEnumList,
+		"immunizationCowType":        s.ImmunizationCowTypeEnumList,
+		"workOrderFrequency":         s.WorkOrderFrequencyEnumList,
+		"workOrderSubUnit":           s.WorkOrderSubUnitEnumList,
+		"workOrderPriority":          s.WorkOrderPriorityEnumList,
+		"workOrderCategory":          s.WorkOrderCategoryEnumList,
+		"immunizationConditions":     s.ImmunizationConditionsEnumList,
+		"abortionReasons":            s.AbortionReasonsEnumList,
+		"healthStatus":               s.HealthStatusEnumList,
+		"calendarType":               CalendarTypeEnumList,
+		"calvingAnalysisMethod":      s.CalvingAnalysisMethodEnumList,
+		"lact":                       s.LactEnumList,
+		"diseaseAnalysisMethod":      s.DiseaseAnalysisMethodEnumList,
+		"singleFactorAnalysisMethod": s.SingleFactorAnalysisMethodEnumList,
+		"lactIntervalSymbol":         s.LactIntervalSymbolEnumList,
 	}
 
 	getConfigFunc, ok := getConfigFuncMap[optionsName]

+ 1 - 0
module/backend/interface.go

@@ -216,6 +216,7 @@ type AnalyseService interface {
 	PregnancyReport(ctx context.Context, req *pasturePb.PregnancyReportRequest, pagination *pasturePb.PaginationModel) (*pasturePb.PregnancyReportResponse, error)
 	CalvingReport(ctx context.Context, req *pasturePb.CalvingReportRequest) (*pasturePb.CalvingReportResponse, error)
 	DiseaseCureReport(ctx context.Context, req *pasturePb.DiseaseCureRateRequest) (*pasturePb.DiseaseCureRateResponse, error)
+	SingleFactorInfantSurvivalRateAnalysis(ctx context.Context, req *pasturePb.SingleFactorPregnancyRateRequest) (*pasturePb.SingleFactorPregnancyRateResponse, error)
 }
 
 //go:generate mockgen -destination mock/DashboardService.go -package kptservicemock kpt-pasture/module/backend DashboardService

+ 31 - 1
util/util.go

@@ -127,7 +127,7 @@ func RoundToTwoDecimals(num float64) float64 {
 }
 
 // Get21DayPeriods 获取范围时间内21天周期的切片
-// 超过开始时间,继续向前推算,直至凑整21天
+// 从结束时间开始往前推算,超过开始时间,继续向前推算,直至凑整21天
 func Get21DayPeriods(startDay, endDay string) ([][]string, error) {
 	startDate, err := time.Parse(Layout, startDay)
 	if err != nil {
@@ -180,3 +180,33 @@ func GetRangeDayMiddleDay(dateRange []string, middleDay int32) (string, error) {
 	startDate, _ := time.Parse(Layout, dateRange[0])
 	return startDate.AddDate(0, 0, int(middleDay)-1).Format(Layout), nil
 }
+
+// GetRangeDayByDays 获取指定范围日期内按照指定天数来切割
+// (2024-10-01 ~ 2024-10-31,5)=> [[2024-10-01,2024-10-05], [2024-10-06,2024-10-10], [2024-10-11,2024-10-15], [2024-10-16,2024-10-20], [2024-10-21,2024-10-25], [2024-10-26,2024-10-30],[2024-10-31,2024-10-31]]
+func GetRangeDayByDays(startDay, endDay string, days int32) ([][]string, error) {
+	var res [][]string
+	startDate, err := time.Parse(Layout, startDay)
+	if err != nil {
+		return nil, err
+	}
+	endDate, err := time.Parse(Layout, endDay)
+	if err != nil {
+		return nil, err
+	}
+	if startDate.After(endDate) {
+		return nil, xerr.Custom("start date is after end date")
+	}
+
+	if startDate == endDate {
+		return [][]string{{startDay, endDay}}, nil
+	}
+
+	for date := startDate; date.Before(endDate) || date.Equal(endDate); date = date.AddDate(0, 0, int(days)) {
+		if date.AddDate(0, 0, int(days)-1).After(endDate) {
+			res = append(res, []string{date.Format(Layout), endDate.Format(Layout)})
+			break
+		}
+		res = append(res, []string{date.Format(Layout), date.AddDate(0, 0, int(days)-1).Format(Layout)})
+	}
+	return res, nil
+}

+ 61 - 0
util/util_test.go

@@ -248,3 +248,64 @@ func TestGet21DayPeriods(t *testing.T) {
 		})
 	}
 }
+
+func TestGetRangeDayByDays(t *testing.T) {
+	tests := []struct {
+		startDay string
+		endDay   string
+		days     int32
+		got      [][]string
+	}{
+		{
+			startDay: "2024-10-23",
+			endDay:   "2024-10-24",
+			days:     5,
+			got: [][]string{
+				{"2024-10-23", "2024-10-24"},
+			},
+		},
+		{
+			startDay: "2024-10-23",
+			endDay:   "2024-10-24",
+			days:     1,
+			got: [][]string{
+				{"2024-10-23", "2024-10-23"},
+				{"2024-10-24", "2024-10-24"},
+			},
+		},
+		{
+			startDay: "2024-10-01",
+			endDay:   "2024-10-31",
+			days:     7,
+			got: [][]string{
+				{"2024-10-01", "2024-10-07"},
+				{"2024-10-08", "2024-10-14"},
+				{"2024-10-15", "2024-10-21"},
+				{"2024-10-22", "2024-10-28"},
+				{"2024-10-29", "2024-10-31"},
+			},
+		},
+		{
+			startDay: "2024-10-01",
+			endDay:   "2024-10-31",
+			days:     5,
+			got: [][]string{
+				{"2024-10-01", "2024-10-05"},
+				{"2024-10-06", "2024-10-10"},
+				{"2024-10-11", "2024-10-15"},
+				{"2024-10-16", "2024-10-20"},
+				{"2024-10-21", "2024-10-25"},
+				{"2024-10-26", "2024-10-30"},
+				{"2024-10-31", "2024-10-31"},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.startDay, func(t *testing.T) {
+			got, err := GetRangeDayByDays(tt.startDay, tt.endDay, tt.days)
+			assert.Nil(t, err)
+			assert.Equal(t, tt.got, got)
+		})
+	}
+}