Browse Source

indicators: 指标对比

Yi 2 months ago
parent
commit
b39b74d6ca

+ 1 - 0
config/app.develop.yaml

@@ -37,6 +37,7 @@ side_work_setting:
 cron:
   crontab_start_run: false
   update_cow_info: "0 01 1 * * ?"       # 每天凌晨1点01分执行
+  indicators: "0 03 1 * * ?"            # 每天凌晨1点03分执行
   generate_work_order: "0 05 1 * * ?"   # 每天凌晨1点05分执行
   immunization_plan: "0 10 1 * * ?"     # 每天凌晨1点10分执行
   same_time_plan: "0 15 1 * * ?"        # 每天凌晨1点15分执行

+ 1 - 0
config/app.go

@@ -51,6 +51,7 @@ type CronSetting struct {
 	CrontabStartRun bool `yaml:"crontab_start_run"`
 	// CRONTAB 表达式
 	UpdateCowInfo      string `yaml:"update_cow_info"`      //  更新牛只信息
+	Indicators         string `yaml:"indicators"`           //  牛只指标
 	GenerateWorkOrder  string `yaml:"generate_work_order"`  //  生成工作单
 	ImmunizationPlan   string `yaml:"immunization_plan"`    //  免疫计划
 	SameTimePlan       string `yaml:"same_time_plan"`       //  同期

+ 1 - 0
config/app.test.yaml

@@ -26,6 +26,7 @@ cache_key_suffix: "gmym"
 cron:
   crontab_start_run: false
   update_cow_info: "0 01 1 * * ?"
+  indicators: "0 03 1 * * ?"
   generate_work_order: "0 05 1 * * ?"
   immunization_plan: "0 10 1 * * ?"
   same_time_plan: "0 15 1 * * ?"

+ 5 - 0
dep/di_crontab.go

@@ -50,6 +50,11 @@ func EntryCrontab(dependency CrontabDependency) *cron.Crontab {
 		panic(err)
 	}
 
+	err = newCrontab.Bind("indicators", cs.Indicators, dependency.CrontabHub.Indicators)
+	if err != nil {
+		panic(err)
+	}
+
 	/*err = newCrontab.Bind("GenerateWorkOrder", cs.GenerateWorkOrder, dependency.CrontabHub.GenerateAsynqWorkOrder)
 	if err != nil {
 		panic(err)

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250123073911-033701a800ce
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250124085242-ce995d9353fb
 	gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3

+ 8 - 0
go.sum

@@ -128,6 +128,14 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250123060356-74f483993d26 h1:bUdNYFdl
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250123060356-74f483993d26/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250123073911-033701a800ce h1:DblWV5HpCOdKgxRxFA/W/DKei/jCcymyYZjoQz+bj/M=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250123073911-033701a800ce/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124055126-a55370977ebd h1:DlMBdv2sOI6zDaov/0tKRHxctTi5tyOH173LooMbKVM=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124055126-a55370977ebd/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124063931-fe501cbad622 h1:15yviP2h/HSJuAWULQzQi/6Hvp81l8ySxbB8XirOluI=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124063931-fe501cbad622/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124065849-a3be4cfe11ab h1:wRGHkMoiyg+JRvt3D5zb+mdU8bXXGgtCWnLr9TXxYhY=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124065849-a3be4cfe11ab/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124085242-ce995d9353fb h1:NYaR6KruC/8osHF5VKierJw+rWCsdQ/sahOUMxtgCl4=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250124085242-ce995d9353fb/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b h1:w05MxH7yqveRlaRbxHhbif5YjPrJFodRPfOjYhXn7Zk=
 gitee.com/xuyiping_admin/pkg v0.0.0-20241108060137-caea58c59f5b/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 23 - 0
http/handler/cow/cow.go

@@ -102,5 +102,28 @@ func GrowthCurve(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
+	ginutil.JSONResp(c, res)
+}
+
+// IndicatorsComparison 指标对比
+func IndicatorsComparison(c *gin.Context) {
+	var req pasturePb.IndicatorsComparisonRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.DateList, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.Dependency(c).StoreEventHub.OpsService.IndicatorsComparison(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
 	c.JSON(http.StatusOK, res)
 }

+ 14 - 14
http/route/analysis_api.go

@@ -11,19 +11,19 @@ func AnalysisAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		for _, opt := range opts {
 			opt(s)
 		}
-		// pasture API 组  牧场管理
-		pastureRoute := authRouteGroup(s, "/api/v1/analysis/")
-		pastureRoute.POST("/growth/curve", analysis.GrowthCurve)
-		pastureRoute.POST("/weight/range", analysis.WeightRange)
-		pastureRoute.POST("/mating/timely", analysis.MatingTimeLy)
-		pastureRoute.POST("/pen/weight", analysis.PenWeight)
-		pastureRoute.POST("/abortion/rate", analysis.AbortionRate)
-		pastureRoute.POST("/twenty/one/pregnant/rate", analysis.TwentyOnePregnantRate)
-		pastureRoute.POST("/pregnancy/report", analysis.PregnancyReport)
-		pastureRoute.POST("/calving/report", analysis.CalvingReport)
-		pastureRoute.POST("/disease/cure/report", analysis.DiseaseCureReport)
-		pastureRoute.POST("/sale/cow/report", analysis.SaleCowReport)
-		pastureRoute.POST("/single/factor/pregnant/report", analysis.SingleFactorInfantSurvivalRate)
-		pastureRoute.POST("/multi/factor/pregnant/report", analysis.MultiFactorInfantSurvivalRate)
+		// analysis API 组
+		analysisRoute := authRouteGroup(s, "/api/v1/analysis/")
+		analysisRoute.POST("/growth/curve", analysis.GrowthCurve)
+		analysisRoute.POST("/weight/range", analysis.WeightRange)
+		analysisRoute.POST("/mating/timely", analysis.MatingTimeLy)
+		analysisRoute.POST("/pen/weight", analysis.PenWeight)
+		analysisRoute.POST("/abortion/rate", analysis.AbortionRate)
+		analysisRoute.POST("/twenty/one/pregnant/rate", analysis.TwentyOnePregnantRate)
+		analysisRoute.POST("/pregnancy/report", analysis.PregnancyReport)
+		analysisRoute.POST("/calving/report", analysis.CalvingReport)
+		analysisRoute.POST("/disease/cure/report", analysis.DiseaseCureReport)
+		analysisRoute.POST("/sale/cow/report", analysis.SaleCowReport)
+		analysisRoute.POST("/single/factor/pregnant/report", analysis.SingleFactorInfantSurvivalRate)
+		analysisRoute.POST("/multi/factor/pregnant/report", analysis.MultiFactorInfantSurvivalRate)
 	}
 }

+ 15 - 15
http/route/config_api.go

@@ -11,20 +11,20 @@ func ConfigAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		for _, opt := range opts {
 			opt(s)
 		}
-		// pasture API 组  牧场管理
-		pastureRoute := authRouteGroup(s, "/api/v1/config/")
-		pastureRoute.GET("/barn/type/options", config.BarnTypeOptions)
-		pastureRoute.GET("/barn/list/options", config.BarnListOptions)
-		pastureRoute.GET("/breed/status/options", config.BreedStatusOptions)
-		pastureRoute.GET("/cow/kind/options", config.CowKindOptions)
-		pastureRoute.GET("/cow/source/options", config.CowSourceOptions)
-		pastureRoute.GET("/cow/type/options", config.CowTypeOptions)
-		pastureRoute.GET("/cow/transfer/pen/reason/options", config.CowTransferPenReasonOptions)
-		pastureRoute.GET("/bull/frozen/semen/options", config.BullListOptions)
-		pastureRoute.GET("/system/user/options", config.SystemUserOptions)
-		pastureRoute.GET("/system/base/config/options", config.SystemBaseConfigOptions)
-		pastureRoute.GET("/disease/type/options", config.DiseaseTypeOptions)
-		pastureRoute.GET("/disease/options", config.DiseaseOptions)
-		pastureRoute.GET("/prescription/options", config.PrescriptionOptions)
+		// config API 组
+		configRoute := authRouteGroup(s, "/api/v1/config/")
+		configRoute.GET("/barn/type/options", config.BarnTypeOptions)
+		configRoute.GET("/barn/list/options", config.BarnListOptions)
+		configRoute.GET("/breed/status/options", config.BreedStatusOptions)
+		configRoute.GET("/cow/kind/options", config.CowKindOptions)
+		configRoute.GET("/cow/source/options", config.CowSourceOptions)
+		configRoute.GET("/cow/type/options", config.CowTypeOptions)
+		configRoute.GET("/cow/transfer/pen/reason/options", config.CowTransferPenReasonOptions)
+		configRoute.GET("/bull/frozen/semen/options", config.BullListOptions)
+		configRoute.GET("/system/user/options", config.SystemUserOptions)
+		configRoute.GET("/system/base/config/options", config.SystemBaseConfigOptions)
+		configRoute.GET("/disease/type/options", config.DiseaseTypeOptions)
+		configRoute.GET("/disease/options", config.DiseaseOptions)
+		configRoute.GET("/prescription/options", config.PrescriptionOptions)
 	}
 }

+ 8 - 5
http/route/cow_api.go

@@ -12,10 +12,13 @@ func CowAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 			opt(s)
 		}
 		// CowAPI API 组  牛只信息
-		eventRoute := authRouteGroup(s, "/api/v1/cow/")
-		eventRoute.POST("/list", cow.List)
-		eventRoute.POST("/event/list", cow.EventList)
-		eventRoute.POST("/behavior/curve", cow.BehaviorCurve)
-		eventRoute.POST("/growth/curve", cow.GrowthCurve)
+		cowRoute := authRouteGroup(s, "/api/v1/cow/")
+		cowRoute.POST("/list", cow.List)
+		cowRoute.POST("/event/list", cow.EventList)
+		cowRoute.POST("/behavior/curve", cow.BehaviorCurve)
+		cowRoute.POST("/growth/curve", cow.GrowthCurve)
+
+		searchRoute := authRouteGroup(s, "/api/v1/search/")
+		searchRoute.POST("/indicators/comparison", cow.IndicatorsComparison)
 	}
 }

+ 5 - 4
http/route/dashboard.go

@@ -1,8 +1,9 @@
 package route
 
 import (
-	"github.com/gin-gonic/gin"
 	"kpt-pasture/http/handler/dashboard"
+
+	"github.com/gin-gonic/gin"
 )
 
 func DashboardApi(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
@@ -10,8 +11,8 @@ func DashboardApi(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		for _, opt := range opts {
 			opt(s)
 		}
-		// CowAPI API 组  牛只信息
-		eventRoute := authRouteGroup(s, "/api/v1/dashboard/")
-		eventRoute.GET("/bar", dashboard.Bar)
+		// dashboard API 组
+		dashboardRoute := authRouteGroup(s, "/api/v1/dashboard/")
+		dashboardRoute.GET("/bar", dashboard.Bar)
 	}
 }

+ 3 - 3
model/event_estrus.go

@@ -21,7 +21,7 @@ type EventEstrus struct {
 	IsPeak           pasturePb.IsShow_Kind           `json:"isPeak"`
 	DayHigh          int32                           `json:"dayHigh"`
 	MaxHigh          int32                           `json:"maxHigh"`
-	Result           pasturePb.EstrusResult_Kind     `json:"result"`
+	CheckResult      pasturePb.CheckResult_Kind      `json:"checkResult"`
 	Remarks          string                          `json:"remarks"`
 	IsShow           pasturePb.IsShow_Kind           `json:"isShow"`
 	OperationId      int64                           `json:"operationId"`
@@ -40,7 +40,7 @@ func NewEventEstrus(
 	pastureId int64,
 	exposeEstrusType pasturePb.ExposeEstrusType_Kind,
 	level pasturePb.EstrusLevel_Kind,
-	result pasturePb.EstrusResult_Kind,
+	result pasturePb.CheckResult_Kind,
 	isShow, isPerk pasturePb.IsShow_Kind,
 	lastEstrusDate, activeDate string,
 	dayHigh, maxHigh int32,
@@ -56,7 +56,7 @@ func NewEventEstrus(
 		ActiveDate:       activeDate,
 		Level:            level,
 		IsShow:           isShow,
-		Result:           result,
+		CheckResult:      result,
 		DayHigh:          dayHigh,
 		MaxHigh:          maxHigh,
 		IsPeak:           isPerk,

+ 67 - 0
model/health_warning.go

@@ -0,0 +1,67 @@
+package model
+
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
+type NeckRingHealthWarning struct {
+	Id                 int64                      `json:"id"`
+	PastureId          int64                      `json:"pastureId"`
+	NeckRingNumber     string                     `json:"neckRingNumber"`
+	CowId              int64                      `json:"cowId"`
+	Lact               int32                      `json:"lact"`
+	CalvingAge         int32                      `json:"calvingAge"`
+	HeatDate           string                     `json:"heatDate"`
+	Frameid            int32                      `json:"frameid"`
+	ActiveTime         string                     `json:"activeTime"`
+	ChangeFilter       int32                      `json:"changeFilter"`
+	ChewFilter         int32                      `json:"chewFilter"`
+	SumChew            int32                      `json:"sumChew"`
+	SumInactive        int32                      `json:"sumInactive"`
+	BeforeThreeSumChew int32                      `json:"beforeThreeSumChew"`
+	MinHigh            int32                      `json:"minHigh"`
+	MaxHigh            int32                      `json:"maxHigh"`
+	MinChew            int32                      `json:"minChew"`
+	MinInactive        int32                      `json:"minInactive"`
+	Score              int32                      `json:"score"`
+	IsWorse            pasturePb.IsShow_Kind      `json:"isWorse"`
+	CheckResult        pasturePb.CheckResult_Kind `json:"checkResult"`
+	IsShow             pasturePb.IsShow_Kind      `json:"isShow"`
+	CreatedAt          int64                      `json:"createdAt"`
+	UpdatedAt          int64                      `json:"updatedAt"`
+}
+
+func (h *NeckRingHealthWarning) TableName() string {
+	return "neck_ring_health_warning"
+}
+
+func NewNeckRingHealthWarning(habit *NeckActiveHabit, sumChew, chew3dago int32) *NeckRingHealthWarning {
+	return &NeckRingHealthWarning{
+		PastureId:          habit.PastureId,
+		NeckRingNumber:     habit.NeckRingNumber,
+		CowId:              habit.CowId,
+		Lact:               habit.Lact,
+		CalvingAge:         habit.CalvingAge,
+		HeatDate:           habit.HeatDate,
+		Frameid:            habit.Frameid,
+		ActiveTime:         habit.ActiveTime,
+		ChangeFilter:       getAbleInt(habit.ChangeFilter),
+		ChewFilter:         getAbleInt(habit.ChewFilter),
+		SumChew:            sumChew,
+		SumInactive:        habit.SumInactive,
+		BeforeThreeSumChew: chew3dago,
+		MinHigh:            getAbleInt(habit.SumMinHigh),
+		MaxHigh:            getAbleInt(habit.SumMaxHigh),
+		MinChew:            getAbleInt(habit.SumMinChew),
+		//MinInactive:        getAbleInt(habit.SumInactive),
+		Score:       habit.Score,
+		IsWorse:     0,
+		CheckResult: pasturePb.CheckResult_Pending,
+		IsShow:      pasturePb.IsShow_No,
+	}
+}
+
+func getAbleInt(value int32) int32 {
+	if value > -99 {
+		return value
+	}
+	return 0
+}

+ 55 - 15
model/indicators_data.go

@@ -1,26 +1,66 @@
 package model
 
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
 type IndicatorsData struct {
-	Id        int64  `json:"id"`
-	PastureId int64  `json:"pastureId"`
-	Year      string `json:"year"`
-	Month     string `json:"month"`
-	Kind      string `json:"kind"`
-	Value     string `json:"value"`
-	CreatedAt int64  `json:"createdAt"`
-	UpdatedAt int64  `json:"updatedAt"`
+	Id           int64                        `json:"id"`
+	PastureId    int64                        `json:"pastureId"`
+	CategoryType pasturePb.IndicatorType_Kind `json:"categoryType"`
+	CategoryName string                       `json:"categoryName"`
+	Date         string                       `json:"date"`
+	Kind         string                       `json:"kind"`
+	Value        string                       `json:"value"`
+	CreatedAt    int64                        `json:"createdAt"`
+	UpdatedAt    int64                        `json:"updatedAt"`
 }
 
 func (d *IndicatorsData) TableName() string {
 	return "indicators_data"
 }
 
-func NewDataIndicators(pastureId int64, year, month, value, indicatorKind string) *IndicatorsData {
-	return &IndicatorsData{
-		PastureId: pastureId,
-		Year:      year,
-		Month:     month,
-		Kind:      indicatorKind,
-		Value:     value,
+type IndicatorsDataSlice []*IndicatorsData
+
+func (i IndicatorsDataSlice) ToPB(detailsMap map[string]*IndicatorsDetails) *IndicatorComparison {
+	res := &IndicatorComparison{
+		Headers: make([]string, 0),
+		List:    make(map[string][]string),
+		Notes:   make(map[string]map[string]string),
+	}
+
+	dateMap := make(map[string]bool)
+	for _, v := range i {
+		if len(res.Headers) == 0 {
+			res.Headers = []string{"指标名称", "单位"}
+		}
+		if !dateMap[v.Date] {
+			dateMap[v.Date] = true
+			res.Headers = append(res.Headers, v.Date)
+		}
+
+		details, ok := detailsMap[v.Kind]
+		if ok {
+			if res.List[v.Kind] == nil {
+				res.List[v.Kind] = make([]string, 0)
+				res.List[v.Kind] = append(res.List[v.Kind], details.Name, details.Unit)
+			}
+			res.List[v.Kind] = append(res.List[v.Kind], v.Value)
+			if res.Notes[v.Kind] == nil {
+				res.Notes[v.Kind] = make(map[string]string)
+			}
+			res.Notes[v.Kind]["zh"] = details.Zh
+		}
 	}
+	return res
+}
+
+type IndicatorsComparisonResponse struct {
+	Code int32                `json:"code"`
+	Msg  string               `json:"msg"`
+	Data *IndicatorComparison `json:"data"`
+}
+
+type IndicatorComparison struct {
+	Headers []string                     `json:"headers"`
+	List    map[string][]string          `json:"list"`
+	Notes   map[string]map[string]string `json:"notes"`
 }

+ 19 - 0
model/indicators_details.go

@@ -0,0 +1,19 @@
+package model
+
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
+type IndicatorsDetails struct {
+	Id           int                          `json:"id"`
+	Kind         string                       `json:"kind"`
+	Name         string                       `json:"name"`
+	CategoryType pasturePb.IndicatorType_Kind `json:"categoryType"`
+	CategoryName string                       `json:"categoryName"`
+	Unit         string                       `json:"unit"`
+	Zh           string                       `json:"zh"`
+	CreatedAt    int64                        `json:"createdAt"`
+	UpdatedAt    int64                        `json:"updatedAt"`
+}
+
+func (IndicatorsDetails) TableName() string {
+	return "indicators_details"
+}

+ 0 - 16
model/indicators_remarks.go

@@ -1,16 +0,0 @@
-package model
-
-type IndicatorsRemarks struct {
-	Id        int    `json:"id"`
-	Kind      string `json:"kind"`
-	Name      string `json:"name"`
-	Unit      string `json:"unit"`
-	Zh        string `json:"zh"`
-	En        string `json:"en"`
-	CreatedAt int64  `json:"createdAt"`
-	UpdatedAt int64  `json:"updatedAt"`
-}
-
-func (IndicatorsRemarks) TableName() string {
-	return "indicators_remarks"
-}

+ 16 - 15
model/system_user.go

@@ -9,21 +9,22 @@ import (
 )
 
 type SystemUser struct {
-	Id        int64                  `json:"id"`
-	PastureId int64                  `json:"pastureId"`
-	Name      string                 `json:"name"`
-	NickName  string                 `json:"nickName"`
-	Mobile    string                 `json:"mobile"`
-	Password  string                 `json:"password"`
-	Avatar    string                 `json:"avatar"`
-	RoleId    int64                  `json:"roleId"`
-	DeptId    int64                  `json:"deptId"`
-	Remarks   string                 `json:"remarks"`
-	Gender    pasturePb.Genders_Kind `json:"gender"`
-	IsShow    pasturePb.IsShow_Kind  `json:"isShow"`
-	IsDelete  pasturePb.IsShow_Kind  `json:"isDelete"`
-	CreatedAt int64                  `json:"created_at"`
-	UpdatedAt int64                  `json:"updated_at"`
+	Id              int64                  `json:"id"`
+	PastureId       int64                  `json:"pastureId"`
+	Name            string                 `json:"name"`
+	NickName        string                 `json:"nickName"`
+	Mobile          string                 `json:"mobile"`
+	Password        string                 `json:"password"`
+	Avatar          string                 `json:"avatar"`
+	RoleId          int64                  `json:"roleId"`
+	DeptId          int64                  `json:"deptId"`
+	IndicatorsKinds string                 `json:"indicatorsKinds"`
+	Remarks         string                 `json:"remarks"`
+	Gender          pasturePb.Genders_Kind `json:"gender"`
+	IsShow          pasturePb.IsShow_Kind  `json:"isShow"`
+	IsDelete        pasturePb.IsShow_Kind  `json:"isDelete"`
+	CreatedAt       int64                  `json:"created_at"`
+	UpdatedAt       int64                  `json:"updated_at"`
 }
 
 func (s *SystemUser) TableName() string {

+ 2 - 2
module/backend/event_breed.go

@@ -400,8 +400,8 @@ func (s *StoreEntry) EstrusBatchMating(ctx context.Context, req *pasturePb.Event
 			if err = tx.Model(new(model.EventEstrus)).
 				Where("id IN ?", eventEstrusIds).
 				Updates(map[string]interface{}{
-					"is_show": pasturePb.IsShow_No,
-					"result":  pasturePb.EstrusResult_Correct,
+					"is_show":      pasturePb.IsShow_No,
+					"check_result": pasturePb.CheckResult_Correct,
 				}).Error; err != nil {
 				return xerr.WithStack(err)
 			}

+ 48 - 0
module/backend/indicators.go

@@ -0,0 +1,48 @@
+package backend
+
+import (
+	"context"
+	"kpt-pasture/model"
+	"net/http"
+
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+	"gitee.com/xuyiping_admin/pkg/xerr"
+)
+
+func (s *StoreEntry) IndicatorsComparison(ctx context.Context, req *pasturePb.IndicatorsComparisonRequest) (*model.IndicatorsComparisonResponse, error) {
+	currentUser, err := s.GetCurrentSystemUser(ctx)
+	if err != nil {
+		return nil, xerr.Custom("当前用户信息错误,请退出重新登录")
+	}
+
+	indicatorsDataList := make([]*model.IndicatorsData, 0)
+	pref := s.DB.Model(new(model.IndicatorsData)).
+		Where("pasture_id = ?", currentUser.PastureId).
+		Where("date IN (?)", req.DateList)
+	if req.CategoryKind > 0 {
+		pref.Where("category_type = ?", req.CategoryKind)
+	}
+
+	if len(req.KindList) > 0 {
+		pref.Where("kind IN (?)", req.KindList)
+	}
+
+	if err = pref.Order("date,kind").Find(&indicatorsDataList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	indicatorsDetails := make(map[string]*model.IndicatorsDetails)
+	indicatorsDetailsList, err := s.FindIndicatorsDetailsList(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	for _, v := range indicatorsDetailsList {
+		indicatorsDetails[v.Kind] = v
+	}
+
+	return &model.IndicatorsComparisonResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: model.IndicatorsDataSlice(indicatorsDataList).ToPB(indicatorsDetails),
+	}, nil
+}

+ 2 - 0
module/backend/interface.go

@@ -213,6 +213,8 @@ type CowService interface {
 	EventList(ctx context.Context, req *pasturePb.SearchCowEventListRequest, pagination *pasturePb.PaginationModel) (*pasturePb.CowEventListResponse, error)
 	BehaviorCurve(ctx context.Context, req *pasturePb.CowBehaviorCurveRequest) (*model.CowBehaviorCurveResponse, error)
 	CowGrowthCurve(ctx context.Context, req *pasturePb.CowGrowthCurveRequest) (*pasturePb.CowGrowthCurveResponse, error)
+
+	IndicatorsComparison(ctx context.Context, req *pasturePb.IndicatorsComparisonRequest) (*model.IndicatorsComparisonResponse, error)
 }
 
 //go:generate mockgen -destination mock/GoodsService.go -package kptservicemock kpt-pasture/module/backend GoodsService

+ 8 - 0
module/backend/sql.go

@@ -414,6 +414,14 @@ func (s *StoreEntry) GetEventLogList(
 	return eventLogList, nil
 }
 
+func (s *StoreEntry) FindIndicatorsDetailsList(ctx context.Context) ([]*model.IndicatorsDetails, error) {
+	list := make([]*model.IndicatorsDetails, 0)
+	if err := s.DB.Model(new(model.IndicatorsDetails)).Find(&list).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return list, nil
+}
+
 func GetCowPenInfoByCowId(DB *kptstore.DB, cowId int64) *model.Pen {
 	penData := &model.Pen{}
 	if err := DB.Model(new(model.Cow)).

+ 24 - 0
module/crontab/cow_cron.go

@@ -56,6 +56,30 @@ func (e *Entry) GenerateAsynqWorkOrder() error {
 	return nil
 }
 
+// Indicators 指标维护
+func (e *Entry) Indicators() error {
+	indicatorsRemarksList := make([]*model.IndicatorsDetails, 0)
+	if err := e.DB.Model(new(model.IndicatorsDetails)).
+		Where("is_show = ?", pasturePb.IsShow_Ok).
+		Find(&indicatorsRemarksList).Error; err != nil {
+		return err
+	}
+	nowTime := time.Now().Format(model.LayoutMonth)
+	for _, v := range indicatorsRemarksList {
+		switch v.Kind {
+		case "all_cow":
+			pastureIdAllCow := e.FindPastureAllCow()
+			for pastureId, value := range pastureIdAllCow {
+				e.UpdatePastureIndicators(pastureId, nowTime, v.Kind, fmt.Sprintf("%d", value))
+			}
+		case "calving_interval":
+
+		}
+	}
+
+	return nil
+}
+
 // UpdateCowInfo 牛只基本信息维护
 func (e *Entry) UpdateCowInfo() error {
 	cowList := make([]*model.Cow, 0)

+ 49 - 0
module/crontab/cow_indicators.go

@@ -0,0 +1,49 @@
+package crontab
+
+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"
+)
+
+// FindPastureAllCow 查询所有牧场牛只总数
+func (e *Entry) FindPastureAllCow() map[int64]int32 {
+	pastureList := e.FindPastureList()
+	res := make(map[int64]int32)
+	for _, pasture := range pastureList {
+		var count int64
+		if err := e.DB.Model(&model.Cow{}).
+			Where("pasture_id = ?", pasture.Id).
+			Where("admission_status = ?", pasturePb.AdmissionStatus_Admission).
+			Count(&count).Error; err != nil {
+			zaplog.Error("FindAllCow", zap.Any("pasture_id", pasture.Id), zap.Any("err", err))
+		}
+		res[pasture.Id] = int32(count)
+	}
+	return res
+}
+
+func (e *Entry) UpdatePastureIndicators(pastureId int64, date, kind, value string) {
+	indicatorsData := &model.IndicatorsData{
+		PastureId: pastureId,
+		Date:      date,
+		Kind:      kind,
+	}
+	res := e.DB.Model(indicatorsData).
+		Where(indicatorsData).
+		FirstOrCreate(indicatorsData)
+	if res.Error != nil {
+		zaplog.Error("UpdatePastureIndicators", zap.Any("err", res.Error))
+		return
+	}
+
+	if res.RowsAffected == 0 {
+		indicatorsData.Value = value
+		if err := e.DB.Save(indicatorsData).Error; err != nil {
+			zaplog.Error("UpdatePastureIndicators", zap.Any("err", err))
+		}
+	}
+}

+ 53 - 0
module/crontab/health_warning.go

@@ -0,0 +1,53 @@
+package crontab
+
+import (
+	"kpt-pasture/model"
+
+	"gitee.com/xuyiping_admin/pkg/xerr"
+)
+
+func (e *Entry) HealthWarning(pastureId int64, processIds []int64) (err error) {
+	newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
+	if err = e.DB.Model(new(model.NeckActiveHabit)).
+		Where("pasture_id = ?", pastureId).
+		Where("id IN (?)", processIds).
+		Where("score BETWEEN ? AND ?", 1, 84).
+		Order("heat_date,neck_ring_number,frameid").
+		Find(&newNeckActiveHabitList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	var (
+		lastCowID         int64
+		lastHeatDate      string
+		lastScore         int32
+		healthWarningList []*model.NeckRingHealthWarning
+	)
+	for _, habit := range newNeckActiveHabitList {
+		// 计算 sumChew 和 chew3dago
+		sumChew := habit.SumRumina + habit.SumIntake
+		chew3dago := habit.BeforeThreeSumRumina + habit.BeforeThreeSumIntake
+		// 判断是否满足 isWorse 条件
+		isWorse := 1
+		if habit.CowId == lastCowID && habit.HeatDate == lastHeatDate && habit.Score >= lastScore {
+			isWorse = 0
+		}
+		// 更新状态
+		lastCowID = habit.CowId
+		lastHeatDate = habit.HeatDate
+		lastScore = habit.Score
+
+		// 如果满足条件,添加到结果中
+		if isWorse == 1 {
+			newHealthWarning := model.NewNeckRingHealthWarning(habit, sumChew, chew3dago)
+			healthWarningList = append(healthWarningList, newHealthWarning)
+		}
+	}
+	if len(healthWarningList) > 0 {
+		if err = e.DB.Create(&healthWarningList).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+
+	return nil
+}

+ 1 - 0
module/crontab/interface.go

@@ -26,6 +26,7 @@ func NewCrontab(entry Entry) Crontab {
 
 type Crontab interface {
 	UpdateCowInfo() error
+	Indicators() error
 	GenerateAsynqWorkOrder() error
 	ImmunizationPlan() error
 	SameTimePlan() error

+ 6 - 6
module/crontab/neck_ring_estrus.go

@@ -232,14 +232,14 @@ func calculateLevel(cft float32, cowEstrus *CowEstrus, xToday *XToday) pasturePb
 }
 
 // getResult 根据b3数据计算结果 0 1 2 3 -1 -2
-func getResult(b3 *model.EventEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.EstrusResult_Kind {
-	result := pasturePb.EstrusResult_Invalid
-	if b3.Result == pasturePb.EstrusResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
-		result = pasturePb.EstrusResult_Fail
+func getResult(b3 *model.EventEstrus, cft float32, cowEstrus *CowEstrus) pasturePb.CheckResult_Kind {
+	result := pasturePb.CheckResult_Invalid
+	if b3.CheckResult == pasturePb.CheckResult_Fail && b3.DayHigh > int32(cft)+cowEstrus.HadJust {
+		result = pasturePb.CheckResult_Fail
 	}
 
-	if b3.Result == pasturePb.EstrusResult_Overdue {
-		result = pasturePb.EstrusResult_Correct
+	if b3.CheckResult == pasturePb.CheckResult_Overdue {
+		result = pasturePb.CheckResult_Correct
 	}
 	return result
 }

+ 24 - 0
module/crontab/neck_ring_handle.go

@@ -308,6 +308,13 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 	if err = e.UpdateChangeAdJust(pastureId, xToday); err != nil {
 		zaplog.Error("EntryUpdateActiveHabit", zap.Any("UpdateChangeAdJust", err), zap.Any("xToday", xToday))
 	}
+
+	// 健康预警
+	if len(processIds) > 0 {
+		if err = e.HealthWarning(pastureId, processIds); err != nil {
+			zaplog.Error("EntryUpdateActiveHabit", zap.Any("HealthWarning", err))
+		}
+	}
 	return nil
 }
 
@@ -643,6 +650,21 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, xToday *XToday) err
 
 // UpdateChangeAdJust 更新群体校正数据
 func (e *Entry) UpdateChangeAdJust(pastureId int64, xToday *XToday) error {
+	/*-- 插入群体校正表
+	INSERT INTO data_bar_change(heatdate, frameid, intCurBar, intCurBarName, nb, highchange, changefilter)
+	SELECT h.heatdate, h.frameid, c.intCurBar, c.intCurBarName, COUNT(*) nb, ROUND(AVG(h.highchange)) highchange, ROUND(AVG(h.changefilter) ) changefilter
+	FROM h_activehabit h JOIN cow c ON h.intCowId=c.intCowId
+	WHERE h.heatdate>=(CURDATE() -INTERVAL 1 DAY )
+	GROUP BY h.heatdate, h.frameid, c.intCurBar
+	ORDER BY h.heatdate, h.frameid, c.intCurBarName
+	ON DUPLICATE KEY UPDATE nb = VALUES(nb), highchange = VALUES(highchange), changefilter = VALUES(changefilter);
+
+	UPDATE h_activehabit h
+	JOIN cow c ON h.intCowId=c.intCowId
+	JOIN data_bar_change cg ON h.heatdate=cg.heatdate AND h.frameid=cg.frameid AND c.intCurBar=cg.intCurBar
+	SET h.changeadjust=cg.changefilter
+	WHERE h.id>xBeg_update_act_Id AND h.heatdate>=CURRENT_DATE() - INTERVAL 1 DAY AND ABS(cg.changefilter)>=10;
+	*/
 	res := make([]*model.NeckRingBarChange, 0)
 	oneDayAgo := time.Now().AddDate(0, 0, -1).Format(model.LayoutDate2)
 	if err := e.DB.Table(fmt.Sprintf("%s as h", new(model.NeckActiveHabit).TableName())).
@@ -651,12 +673,14 @@ func (e *Entry) UpdateChangeAdJust(pastureId int64, xToday *XToday) error {
 		Where("h.pasture_id = ?", pastureId).
 		Where("h.heat_date >= ?", oneDayAgo).
 		Where("h.cow_id >= ?", 0).
+		Where("h.cow_id >= ?", 0).
 		Group("h.heat_date, h.frameid, c.pen_id").
 		Order("h.heat_date, h.frameid, c.pen_name").
 		Find(&res).Error; err != nil {
 		return xerr.WithStack(err)
 	}
 
+	// todo ABS(cg.changefilter)>=10;
 	for _, v := range res {
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
 			Where("id > ?", xToday.LastMaxHabitId).

+ 4 - 3
util/util_test.go

@@ -508,7 +508,8 @@ func TestGetNeckRingActiveTimer(t *testing.T) {
 
 func Test_demo(t *testing.T) {
 
-	beginDayDate, _ := time.Parse("2006-01-02", "2025-01-23")
-	before3DayDate := beginDayDate.AddDate(0, 0, -3).Format("2006-01-02")
-	fmt.Println(before3DayDate)
+	nowTime := time.Now()
+	year := nowTime.Year()
+	month := nowTime.Month()
+	fmt.Println(year, int32(month))
 }