Prechádzať zdrojové kódy

crontab: neck_ring_calculate update

Yi 1 týždeň pred
rodič
commit
c19f3ed43a

+ 2 - 1
config/app.develop.yaml

@@ -49,7 +49,8 @@ cron:
   neck_ring_estrus: "0 45 * * * ?"      # 更新脖环发情数据
   neck_ring_merge: "*/5 * * * * ?"      # 合并脖环原始2小时数据(5分钟)
   neck_ring_calculate: "*/10 * * * * ?" # 计算脖环数据
-  neck_ring_warning: "*/50 * * * * ?"   # 脖环预警(每50分钟执行一次
+  neck_ring_estrus_warning: "*/50 * * * * ?"   # 脖环预警(每50分钟执行一次
+  neck_ring_health_warning: "*/50 * * * * ?"   # 脖环预警(每50分钟执行一次
 
 mqtt:
   broker: "kptyun.com:1983"

+ 2 - 1
config/app.go

@@ -64,7 +64,8 @@ type CronSetting struct {
 	NeckRingEstrus          string `yaml:"neck_ring_estrus"`           //  脖环牛只发情
 	NeckRingMerge           string `yaml:"neck_ring_merge"`            //  脖环原始数据合并
 	NeckRingCalculate       string `yaml:"neck_ring_calculate"`        //  脖环数据计算
-	NeckRingWarning         string `yaml:"neck_ring_warning"`          //  脖环预警
+	NeckRingEstrusWarning   string `yaml:"neck_ring_estrus_warning"`   //  脖环发情预警
+	NeckRingHealthWarning   string `yaml:"neck_ring_health_warning"`   //  脖环健康预警
 }
 
 type JwtTokenKeyConfig struct {

+ 2 - 0
config/app.test.yaml

@@ -36,6 +36,8 @@ cron:
   neck_ring_estrus: "0 45 * * * ?"      # 更新脖环发情数据
   neck_ring_merge: "*/5 * * * * ?"      # 合并脖环原始2小时数据(5分钟)
   neck_ring_calculate: "*/10 * * * * ?"  # 计算脖环数据
+  neck_ring_estrus_warning: "*/50 * * * * ?"   # 脖环预警(每50分钟执行一次
+  neck_ring_health_warning: "*/50 * * * * ?"   # 脖环预警(每50分钟执行一次
 
 mqtt:
   broker: "kptyun.com:1983"

+ 8 - 2
dep/di_crontab.go

@@ -104,9 +104,15 @@ func EntryCrontab(dependency CrontabDependency) *cron.Crontab {
 		panic(err)
 	}
 
-	err = newCrontab.Bind("NeckRingWarning", cs.NeckRingWarning, dependency.CrontabHub.UpdateNeckRingWarning)
+	err = newCrontab.Bind("NeckRingEstrusWarning", cs.NeckRingEstrusWarning, dependency.CrontabHub.NeckRingEstrusWarning)
 	if err != nil {
-		zaplog.Error("EntryCrontab", zap.Any("NeckRingOriginalMergeData", err))
+		zaplog.Error("EntryCrontab", zap.Any("NeckRingEstrusWarning", err))
+		panic(err)
+	}
+
+	err = newCrontab.Bind("NeckRingHealthWarning", cs.NeckRingHealthWarning, dependency.CrontabHub.NeckRingHealthWarning)
+	if err != nil {
+		zaplog.Error("EntryCrontab", zap.Any("NeckRingHealthWarning", err))
 		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-20250309141840-4e483354df00
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250313095848-b25e11bbb7d0
 	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

+ 4 - 0
go.sum

@@ -38,6 +38,8 @@ 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-20250309141840-4e483354df00 h1:ejRZ6U1vTFjo1VzcHpnraCrqZ6IhkqxM7iMgof2jE90=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250309141840-4e483354df00/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250313095848-b25e11bbb7d0 h1:OqYENfy4l9CwmAqvBqL88K5eAb7CVQC4QHdxCU67du8=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250313095848-b25e11bbb7d0/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=
@@ -172,6 +174,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=
@@ -609,6 +612,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=

+ 1 - 1
http/handler/goods/drugs.go

@@ -154,7 +154,7 @@ func NeckRingCreateOrUpdate(c *gin.Context) {
 			if req.Status == pasturePb.NeckRingOperationStatus_Bind {
 				return valid.ValidateStruct(&item,
 					valid.Field(&item.Number, valid.Required),
-					valid.Field(&item.CowId, valid.Required),
+					valid.Field(&item.EarNumber, valid.Required),
 				)
 			} else {
 				return valid.ValidateStruct(&item,

+ 13 - 4
model/frozen_semen.go

@@ -1,6 +1,8 @@
 package model
 
-import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+import (
+	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+)
 
 type FrozenSemen struct {
 	Id              int64                          `json:"id"`
@@ -34,12 +36,19 @@ func NewFrozenSemen(pastureId int64, req *pasturePb.SearchFrozenSemenList, curre
 	}
 }
 
-func (e *FrozenSemen) TableName() string {
+func (f *FrozenSemen) TableName() string {
 	return "frozen_semen"
 }
 
-func (e *FrozenSemen) EventQuantityUpdate(quantity int32) {
-	e.Quantity -= quantity
+func (f *FrozenSemen) EventQuantityUpdate(quantity int32) {
+	f.Quantity -= quantity
+}
+
+func (f *FrozenSemen) UpdateData(req *pasturePb.SearchFrozenSemenList) {
+	f.Quantity = req.Quantity
+	f.KindId = req.CowKind
+	f.KindName = req.CowKindName
+	f.FrozenSemenType = req.FrozenSemenType
 }
 
 type FrozenSemenSlice []*FrozenSemen

+ 4 - 7
model/neck_active_habit.go

@@ -6,7 +6,6 @@ import (
 	"math"
 	"strconv"
 	"strings"
-	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
 )
@@ -67,7 +66,6 @@ type NeckActiveHabit struct {
 	BeforeThreeSumRumina int32                 `json:"beforeThreeSumRumina"`
 	BeforeThreeSumIntake int32                 `json:"beforeThreeSumIntake"`
 	Score                int32                 `json:"score"`
-	IsMaxTime            pasturePb.IsShow_Kind `json:"isMaxTime"`
 	IsShow               pasturePb.IsShow_Kind `json:"isShow"`
 	RecordCount          int32                 `json:"recordCount"`
 	FirmwareVersion      int32                 `json:"firmwareVersion"`
@@ -108,7 +106,6 @@ func NewNeckActiveHabit(data *NeckRingOriginalMerge) *NeckActiveHabit {
 		Rumina:          data.Rumina,
 		IsShow:          data.IsShow,
 		WeekHigh:        DefaultWeeklyActive,
-		IsMaxTime:       pasturePb.IsShow_No,
 		ChangeFilter:    InitChangeFilter,
 		FilterCorrect:   InitChangeFilter,
 		RuminaFilter:    InitChangeFilter,
@@ -183,17 +180,17 @@ func (n NeckActiveHabitSlice) ToPB(curveName string) *CowBehaviorCurveData {
 		}
 
 		// 格式化为到小时的字符串
-		parsedTime, _ := time.Parse(LayoutTime, v.ActiveTime)
+		parsedTime, _ := util.TimeParseLocal(LayoutTime, v.ActiveTime)
 		hourStr := parsedTime.Format(LayoutHour)
 		res.DateTimeList = append(res.DateTimeList, hourStr)
 		switch curveName {
 		case "active": // 活动量
 			res.OriginalDateList = append(res.OriginalDateList, v.High)
-			res.ChangeDateList = append(res.ChangeDateList, v.ChangeFilter)
-			res.RuminaChange = append(res.RuminaChange, v.ChangeRumina)
+			res.ChangeDateList = append(res.ChangeDateList, v.ChangeFilter*v.FilterCorrect/100)
+			res.RuminaChange = append(res.RuminaChange, v.FilterRumina)
 		case "rumina": // 反刍
 			res.OriginalDateList = append(res.OriginalDateList, v.Rumina)
-			res.ChangeDateList = append(res.ChangeDateList, v.ChangeRumina)
+			res.ChangeDateList = append(res.ChangeDateList, v.FilterRumina)
 			res.SumDateList = append(res.SumDateList, v.SumRumina)
 		case "intake": // 采食
 			res.OriginalDateList = append(res.OriginalDateList, v.Intake)

+ 1 - 0
model/neck_ring_configure.go

@@ -12,6 +12,7 @@ const (
 	ActiveMiddle    = "active_middle"
 	ActiveHigh      = "active_high"
 	MaxHabit        = "max_habit"
+	HealthWarning   = "health_warning"
 )
 
 type NeckRingConfigure struct {

+ 4 - 3
model/neck_ring_estrus_warning.go

@@ -1,6 +1,7 @@
 package model
 
 import (
+	"kpt-pasture/util"
 	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
@@ -53,8 +54,8 @@ func NewNeckRingEstrusWarning(
 // calculatePzHour 计算最佳配置时间
 func (n *NeckRingEstrusWarning) calculatePzHour(lact int32) time.Time {
 	var pzHour time.Time
-	dateTime, _ := time.Parse(LayoutTime, n.DateTime)
-	firstTime, _ := time.Parse(LayoutTime, n.FirstTime)
+	dateTime, _ := util.TimeParseLocal(LayoutTime, n.DateTime)
+	firstTime, _ := util.TimeParseLocal(LayoutTime, n.FirstTime)
 
 	// 条件判断
 	if n.IsPeak == pasturePb.IsShow_Ok || dateTime.Sub(firstTime).Hours() >= 8 {
@@ -88,7 +89,7 @@ func (n NeckRingEstrusWarningSlice) ToPB(cowMap map[int64]*Cow, eventLogMap map[
 		pzHour := v.calculatePzHour(cow.Lact)
 		estrusInterval := int32(0)
 		if v.LastTime != "" {
-			lastTime, _ := time.Parse(LayoutTime, v.LastTime)
+			lastTime, _ := util.TimeParseLocal(LayoutTime, v.LastTime)
 			diff := pzHour.Sub(lastTime)
 			estrusInterval = int32(diff.Hours() / 24)
 		}

+ 70 - 0
model/neck_ring_health.go

@@ -0,0 +1,70 @@
+package model
+
+import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
+
+const (
+	MinScore = 1
+	MaxScore = 84
+)
+
+type NeckRingHealth 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"`
+	CreatedAt          int64                      `json:"createdAt"`
+	UpdatedAt          int64                      `json:"updatedAt"`
+}
+
+func (h *NeckRingHealth) TableName() string {
+	return "neck_ring_health"
+}
+
+func NewNeckRingHealth(habit *NeckActiveHabit, sumChew, chew3dago int32) *NeckRingHealth {
+	return &NeckRingHealth{
+		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,
+	}
+}
+
+func getAbleInt(value int32) int32 {
+	if value > -99 {
+		return value
+	}
+	return 0
+}

+ 29 - 61
model/neck_ring_health_warning.go

@@ -1,72 +1,40 @@
 package model
 
-import pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
-
-const (
-	MinScore = 1
-	MaxScore = 84
-)
-
 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"`
+	Id                 int64  `json:"id"`
+	NeckRingHealthId   int64  `json:"neckRingHealthId"`
+	PastureId          int64  `json:"pastureId"`
+	CowId              int64  `json:"cowId"`
+	EarNumber          string `json:"earNumber"`
+	NeckRingNumber     string `json:"neckRingNumber"`
+	HeatDate           string `json:"heatDate"`
+	MinHigh            int32  `json:"minHigh"`
+	MinChew            int32  `json:"minChew"`
+	MinIntake          int32  `json:"minIntake"`
+	ChewSum            int32  `json:"chewSum"`
+	BeforeThreeSumChew int32  `json:"beforeThreeSumChew"`
+	Score              int32  `json:"score"`
+	CreatedAt          int64  `json:"created"`
+	UpdatedAt          int64  `json:"updated"`
 }
 
-func (h *NeckRingHealthWarning) TableName() string {
+func (n *NeckRingHealthWarning) TableName() string {
 	return "neck_ring_health_warning"
 }
 
-func NewNeckRingHealthWarning(habit *NeckActiveHabit, sumChew, chew3dago int32) *NeckRingHealthWarning {
+func NewNeckRingHealthWarning(pastureId int64, neckRingHealth *NeckRingHealth, cow *Cow, newScore 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
+		NeckRingHealthId:   pastureId,
+		PastureId:          neckRingHealth.Id,
+		CowId:              cow.Id,
+		EarNumber:          cow.EarNumber,
+		NeckRingNumber:     cow.NeckRingNumber,
+		HeatDate:           "",
+		MinHigh:            neckRingHealth.MinHigh,
+		MinChew:            neckRingHealth.MinChew,
+		MinIntake:          neckRingHealth.MinInactive,
+		ChewSum:            neckRingHealth.SumChew,
+		BeforeThreeSumChew: neckRingHealth.BeforeThreeSumChew,
+		Score:              newScore,
 	}
-	return 0
 }

+ 1 - 1
model/pen.go

@@ -73,7 +73,7 @@ func (p PenSlice) ToPB2(req []*pasturePb.ConfigOptionsList, isAll string) []*pas
 			label = fmt.Sprintf("%s-%s", label, r.Label)
 		}
 		res = append(res, &pasturePb.ConfigOptionsList{
-			Value:    int32(d.Id),
+			Value:    d.Id,
 			Label:    label,
 			Disabled: true,
 		})

+ 3 - 3
module/backend/analysis.go

@@ -31,8 +31,8 @@ func (s *StoreEntry) WeightScatterPlot(ctx context.Context, req *pasturePb.Searc
 	}
 
 	if len(req.BirthDate) == 2 && req.BirthDate[0] != "" && req.BirthDate[1] != "" {
-		t0, _ := time.Parse(time.RFC3339, req.BirthDate[0])
-		t1, _ := time.Parse(time.RFC3339, req.BirthDate[1])
+		t0, _ := util.TimeParseLocal(model.LayoutDate2, req.BirthDate[0])
+		t1, _ := util.TimeParseLocal(model.LayoutDate2, req.BirthDate[1])
 
 		pref.Where("birth_at BETWEEN ? AND ?", t0.Unix(), t1.Unix()+86399)
 	}
@@ -234,7 +234,7 @@ func (s *StoreEntry) MatingTimely(ctx context.Context, req *pasturePb.MatingTime
 		}, nil
 	}
 	for _, v := range matingTimelyChart {
-		t, _ := time.Parse(model.LayoutDate2, v.RealityDay)
+		t, _ := util.TimeParseLocal(model.LayoutDate2, v.RealityDay)
 		switch v.LactGroup {
 		case "0":
 			chart.Lact0 = append(chart.Lact0, []string{fmt.Sprintf("%d", t.Day()), fmt.Sprintf("%d", v.CalvingAge), v.RealityDay})

+ 1 - 1
module/backend/config_data.go

@@ -18,7 +18,7 @@ func (s *StoreEntry) BarnTypeEnumList() []*pasturePb.ConfigOptionsList {
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.PenType_Youth),
-		Label:    "育成牛舍",
+		Label:    "青年牛舍",
 		Disabled: true,
 	}, &pasturePb.ConfigOptionsList{
 		Value:    int32(pasturePb.PenType_Nurturing),

+ 1 - 2
module/backend/dashboard.go

@@ -47,9 +47,8 @@ func (s *StoreEntry) NeckRingWarning(ctx context.Context) (*pasturePb.IndexNeckR
 	}
 
 	healthWarningNumber := int64(0)
-	if err = s.DB.Model(new(model.NeckRingHealthWarning)).
+	if err = s.DB.Model(new(model.NeckRingHealth)).
 		Where("pasture_id = ?", userModel.AppPasture.Id).
-		Where("is_show = ?", pasturePb.IsShow_Ok).
 		Group("cow_id").
 		Count(&healthWarningNumber).Error; err != nil {
 		zaplog.Error("NeckRingWarning", zap.Any("estrusWarningNumber", err))

+ 7 - 1
module/backend/enum_options.go

@@ -20,13 +20,19 @@ func (s *StoreEntry) BarnTypeOptions(ctx context.Context) (*pasturePb.ConfigOpti
 }
 
 func (s *StoreEntry) BarnListOptions(ctx context.Context, penType int, isAll string) (*pasturePb.ConfigOptionsListResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
 	penList := make([]*model.Pen, 0)
 	pref := s.DB.Table(new(model.Pen).TableName()).
+		Where("pasture_id = ?", userModel.AppPasture.Id).
 		Where("is_delete = ?", pasturePb.IsShow_Ok)
 	if penType != -1 {
 		pref.Where("pen_type = ?", penType)
 	}
-	if err := pref.Find(&penList).Error; err != nil {
+	if err = pref.Find(&penList).Error; err != nil {
 		return nil, err
 	}
 

+ 4 - 0
module/backend/event_base.go

@@ -101,6 +101,10 @@ func (s *StoreEntry) CreateEnter(ctx context.Context, req *pasturePb.EventEnterR
 	}
 
 	penMap := s.PenMap(ctx, userModel.AppPasture.Id)
+	if len(penMap) <= 0 {
+		return xerr.Customf("请先设置牛舍信息")
+	}
+
 	newCow := model.NewEnterCow(userModel.AppPasture.Id, req, penMap)
 	defer func() {
 		if err == nil {

+ 2 - 2
module/backend/event_cow_log.go

@@ -4,9 +4,9 @@ import (
 	"context"
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"strconv"
 	"strings"
-	"time"
 
 	"gitee.com/xuyiping_admin/pkg/xerr"
 
@@ -39,7 +39,7 @@ func (s *StoreEntry) SubmitEventLog(ctx context.Context, pastureId int64, cow *m
 			strconv.FormatFloat(float64(data.Price), 'f', 2, 64), sourceMap[cow.SourceId])
 	case pasturePb.EventType_Transfer_Ben:
 		data := req.(*model.EventTransferGroup)
-		transferAt, _ := time.Parse(model.LayoutTime, data.TransferDate)
+		transferAt, _ := util.TimeParseLocal(model.LayoutTime, data.TransferDate)
 		eventAt = transferAt.Unix()
 		remarks = data.Remarks
 		operationUser.Id = data.OperationId

+ 34 - 6
module/backend/goods.go

@@ -140,7 +140,7 @@ func (s *StoreEntry) NeckRingCreateOrUpdate(ctx context.Context, req *pasturePb.
 	if err = s.DB.Transaction(func(tx *gorm.DB) error {
 		for _, item := range req.Items {
 			number := ""
-			cowInfo, err := s.GetCowInfoByCowId(ctx, userModel.AppPasture.Id, int64(item.CowId))
+			cowInfo, err := s.GetCowInfoByEarNumber(ctx, userModel.AppPasture.Id, item.EarNumber)
 			if err != nil {
 				return xerr.Customf("该牛: %d 不存在", item.CowId)
 			}
@@ -592,9 +592,15 @@ func (s *StoreEntry) OutboundDelete(ctx context.Context, id int64) error {
 }
 
 func (s *StoreEntry) FrozenSemenList(ctx context.Context, req *pasturePb.FrozenSemenRequest, pagination *pasturePb.PaginationModel) (*pasturePb.FrozenSemenResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
 	frozenSemenList := make([]*model.FrozenSemen, 0)
 	var count int64 = 0
-	pref := s.DB.Table(new(model.FrozenSemen).TableName())
+	pref := s.DB.Table(new(model.FrozenSemen).TableName()).
+		Where("pasture_id = ?", userModel.AppPasture.Id)
 	if req.BullId != "" {
 		pref.Where("bull_id = ?", req.BullId)
 	}
@@ -603,7 +609,7 @@ func (s *StoreEntry) FrozenSemenList(ctx context.Context, req *pasturePb.FrozenS
 		pref.Where("producer = ?", req.Producer)
 	}
 
-	if err := pref.Order("id desc").
+	if err = pref.Order("id desc").
 		Count(&count).Limit(int(pagination.PageSize)).
 		Offset(int(pagination.PageOffset)).
 		Find(&frozenSemenList).Error; err != nil {
@@ -630,10 +636,32 @@ func (s *StoreEntry) FrozenSemenCreate(ctx context.Context, req *pasturePb.Searc
 	if err != nil {
 		return xerr.WithStack(err)
 	}
+
 	req.CowKindName = s.CowKindMap()[req.CowKind]
-	newFrozenSemen := model.NewFrozenSemen(userModel.AppPasture.Id, req, userModel.SystemUser)
-	if err := s.DB.Create(newFrozenSemen).Error; err != nil {
-		return xerr.WithStack(err)
+	if req.Id > 0 {
+		histData := &model.FrozenSemen{}
+		if err = s.DB.Model(new(model.FrozenSemen)).
+			Where("id = ?", req.Id).
+			Where("pasture_id = ?", userModel.AppPasture.Id).
+			First(histData).
+			Error; err != nil {
+			return xerr.Customf("该数据不存在:%d", req.Id)
+		}
+
+		histData.UpdateData(req)
+		if err = s.DB.Model(new(model.FrozenSemen)).
+			Where("id = ?", req.Id).
+			Where("pasture_id = ?", userModel.AppPasture.Id).
+			Updates(histData).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+	} else {
+		newFrozenSemen := model.NewFrozenSemen(userModel.AppPasture.Id, req, userModel.SystemUser)
+		if err = s.DB.Create(newFrozenSemen).Error; err != nil {
+			return xerr.WithStack(err)
+		}
 	}
+
 	return nil
 }

+ 14 - 3
module/backend/pasture.go

@@ -14,15 +14,25 @@ import (
 )
 
 func (s *StoreEntry) SearchBarnList(ctx context.Context, req *pasturePb.SearchNameRequest, pagination *pasturePb.PaginationModel) (*pasturePb.SearchBarnResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
 	penList := make([]*model.Pen, 0)
 	var count int64 = 0
 
-	pref := s.DB.Model(new(model.Pen)).Where("is_delete = ?", pasturePb.IsShow_Ok)
+	pref := s.DB.Model(new(model.Pen)).
+		Where("is_delete = ?", pasturePb.IsShow_Ok).
+		Where("pasture_id = ?", userModel.AppPasture.Id)
 	if req.Name != "" {
 		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
 	}
 
-	if err := pref.Order("id desc").Count(&count).Limit(int(pagination.PageSize)).Offset(int(pagination.PageOffset)).
+	if err = pref.Order("id desc").
+		Count(&count).
+		Limit(int(pagination.PageSize)).
+		Offset(int(pagination.PageOffset)).
 		Find(&penList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -57,7 +67,8 @@ func (s *StoreEntry) CreateOrUpdateBarn(ctx context.Context, req *pasturePb.Sear
 	}
 
 	if err = s.DB.Model(&model.Pen{}).Where(map[string]interface{}{
-		"id": req.Id,
+		"id":         req.Id,
+		"pasture_id": userModel.AppPasture.Id,
 	}).Assign(map[string]interface{}{
 		"name":               req.Name,
 		"remarks":            req.Remarks,

+ 44 - 42
module/crontab/estrus_warning.go

@@ -3,6 +3,7 @@ package crontab
 import (
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"sort"
 	"time"
 
@@ -12,28 +13,28 @@ import (
 	"go.uber.org/zap"
 )
 
-func (e *Entry) UpdateNeckRingWarning() (err error) {
+// NeckRingEstrusWarning 脖环发情预警
+func (e *Entry) NeckRingEstrusWarning() (err error) {
 	pastureList := e.FindPastureList()
 	if pastureList == nil || len(pastureList) == 0 {
 		return nil
 	}
-
 	for _, pasture := range pastureList {
-		// 先删除历史数据
-		if err = e.DB.Model(new(model.NeckRingEstrusWarning)).
-			Where("pasture_id = ?", pasture.Id).
-			Delete(new(model.NeckRingEstrusWarning)).Error; err != nil {
-			zaplog.Error("UpdateNeckRingWarning", zap.Any("delete", err), zap.Any("pasture", pasture))
-		}
-		if err = e.NeckRingWarning(pasture.Id); err != nil {
-			zaplog.Error("UpdateNeckRingWarning", zap.Any("NeckRingWarning", err), zap.Any("pasture", pasture))
+		if err = e.UpdateNeckRingWarning(pasture.Id); err != nil {
+			zaplog.Error("UpdateNeckRingWarning", zap.Any("NeckRingEstrusWarning", err), zap.Any("pasture", pasture))
 		}
-		zaplog.Info("UpdateNeckRingWarning", zap.Any("success", pasture.Id))
 	}
 	return nil
 }
 
-func (e *Entry) NeckRingWarning(pastureId int64) (err error) {
+func (e *Entry) UpdateNeckRingWarning(pastureId int64) (err error) {
+	// 先删除历史数据
+	if err = e.DB.Model(new(model.NeckRingEstrusWarning)).
+		Where("pasture_id = ?", pastureId).
+		Delete(new(model.NeckRingEstrusWarning)).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
 	// 计算时间范围
 	now := time.Now()
 	startTime := now.Add(-24 * time.Hour)
@@ -68,18 +69,40 @@ func (e *Entry) NeckRingWarning(pastureId int64) (err error) {
 	}
 
 	minId := e.getMinId(pastureId)
-	/*estrusWarningList, err := e.GetCowHighChange(pastureId, minId)
-	if err != nil {
-		return xerr.WithStack(err)
+	// 更新HighChange字段
+	// e.UpdateHighChange(pastureId,minId)
+	e.UpdateNeckRingWarningIsPeak(pastureId, minId)
+
+	return nil
+}
+
+func (e *Entry) UpdateNeckRingWarningIsPeak(pastureId, minId int64) {
+	sqlQuery := e.DB.Table(fmt.Sprintf("%s as a", new(model.NeckActiveHabit).TableName())).
+		Select("1").
+		Where("a.id >= ?", minId).
+		Where("a.cow_id = b.cow_id").
+		Where("a.created_at > UNIX_TIMESTAMP(b.date_time)")
+
+	if err := e.DB.Table(fmt.Sprintf("%s as b", new(model.NeckRingEstrusWarning).TableName())).
+		Where("b.pasture_id = ?", pastureId).
+		Where("EXISTS (?)", sqlQuery).Update("is_peak", pasturePb.IsShow_Ok).Error; err != nil {
+		zaplog.Error("UpdateNeckRingWarningIsPeak", zap.Any("err", err))
 	}
-	zaplog.Info("UpdateNeckRingWarning", zap.Any("estrusWarningList", estrusWarningList))
+}
 
+func (e *Entry) UpdateHighChange(pastureId, minId int64) {
+	estrusWarningList, err := e.GetCowHighChange(pastureId, minId)
+	if err != nil {
+		zaplog.Error("UpdateHighChange", zap.Any("err", err))
+		return
+	}
+	zaplog.Info("UpdateHighChange", zap.Any("estrusWarningList", estrusWarningList))
 	for _, v := range estrusWarningList {
 		neckRingEstrusWarning := &model.NeckRingEstrusWarning{}
 		if err = e.DB.Model(new(model.NeckRingEstrusWarning)).
 			Where("neck_ring_estrus_id = ?", v.NeckRingEstrusId).
 			Find(neckRingEstrusWarning).Error; err != nil {
-			zaplog.Error("UpdateNeckRingWarning", zap.Any("Find", err), zap.Any("v", v))
+			zaplog.Error("UpdateHighChange", zap.Any("Find", err), zap.Any("v", v))
 			continue
 		}
 
@@ -90,7 +113,7 @@ func (e *Entry) NeckRingWarning(pastureId int64) (err error) {
 					Where("neck_ring_estrus_id = ?", v.NeckRingEstrusId).
 					Where("cow_id = ?", v.CowId).
 					Where("pasture_id = ?", pastureId).Delete(new(model.NeckRingEstrusWarning)).Error; err != nil {
-					zaplog.Error("UpdateNeckRingWarning", zap.Any("Delete", err), zap.Any("v", v))
+					zaplog.Error("UpdateHighChange", zap.Any("Delete", err), zap.Any("v", v))
 				}
 				continue
 			}
@@ -102,31 +125,10 @@ func (e *Entry) NeckRingWarning(pastureId int64) (err error) {
 				"warning_kind": pasturePb.Warning_Estrus,
 				"high_change":  v.HighChange,
 			}).Error; err != nil {
-			zaplog.Error("UpdateNeckRingWarning", zap.Any("Updates", err), zap.Any("v", v))
+			zaplog.Error("UpdateHighChange", zap.Any("Updates", err), zap.Any("v", v))
 			continue
 		}
-	}*/
-
-	if err = e.UpdateNeckRingWarningIsPeak(pastureId, minId); err != nil {
-		zaplog.Error("UpdateNeckRingWarning", zap.Any("UpdateNeckRingWarningIsPeak", err), zap.Any("pastureId", pastureId))
 	}
-
-	return nil
-}
-
-func (e *Entry) UpdateNeckRingWarningIsPeak(pastureId, minId int64) error {
-	sqlQuery := e.DB.Table(fmt.Sprintf("%s as a", new(model.NeckActiveHabit).TableName())).
-		Select("1").
-		Where("a.id >= ?", minId).
-		Where("a.cow_id = b.cow_id").
-		Where("a.created_at > UNIX_TIMESTAMP(b.date_time)")
-
-	if err := e.DB.Table(fmt.Sprintf("%s as b", new(model.NeckRingEstrusWarning).TableName())).
-		Where("b.pasture_id = ?", pastureId).
-		Where("EXISTS (?)", sqlQuery).Update("is_peak", pasturePb.IsShow_Ok).Error; err != nil {
-		return xerr.WithStack(err)
-	}
-	return nil
 }
 
 func (e *Entry) GroupAndProcessData(records []*model.NeckRingEstrus) []*model.NeckRingEstrusWarning {
@@ -238,7 +240,7 @@ func (e *Entry) getMinId(pastureId int64) int64 {
 }
 
 func (e *Entry) getCowHigh(pastureId, cowId, minId int64, dateTime string) int64 {
-	dateTimeUnix, _ := time.Parse(model.LayoutTime, dateTime)
+	dateTimeUnix, _ := util.TimeParseLocal(model.LayoutTime, dateTime)
 	dateTimeUnixStart := time.Time{}
 	if dateTimeUnix.IsZero() {
 		dateTimeUnixStart = time.Now().Add(-22 * time.Hour)
@@ -268,7 +270,7 @@ func findMaxTime(records []*model.NeckRingEstrus, getter func(*model.NeckRingEst
 		if t1 == "" {
 			continue
 		}
-		t, err := time.Parse(model.LayoutTime, t1)
+		t, err := util.TimeParseLocal(model.LayoutTime, t1)
 		if err != nil {
 			continue
 		}

+ 89 - 2
module/crontab/health_warning.go

@@ -2,6 +2,9 @@ package crontab
 
 import (
 	"kpt-pasture/model"
+	"time"
+
+	"gitee.com/xuyiping_admin/pkg/xerr"
 
 	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"go.uber.org/zap"
@@ -22,7 +25,7 @@ func (e *Entry) HealthWarning(pastureId int64, processIds []int64) {
 		lastCowID         int64
 		lastHeatDate      string
 		lastScore         int32
-		healthWarningList []*model.NeckRingHealthWarning
+		healthWarningList []*model.NeckRingHealth
 	)
 	for _, habit := range newNeckActiveHabitList {
 		// 计算 sumChew 和 chew3dago
@@ -40,7 +43,10 @@ func (e *Entry) HealthWarning(pastureId int64, processIds []int64) {
 
 		// 如果满足条件,添加到结果中
 		if isWorse == 1 {
-			newHealthWarning := model.NewNeckRingHealthWarning(habit, sumChew, chew3dago)
+			newHealthWarning := model.NewNeckRingHealth(habit, sumChew, chew3dago)
+			if count := e.FindHealthWarning(pastureId, newHealthWarning); count <= 0 {
+				continue
+			}
 			healthWarningList = append(healthWarningList, newHealthWarning)
 		}
 	}
@@ -50,3 +56,84 @@ func (e *Entry) HealthWarning(pastureId int64, processIds []int64) {
 		}
 	}
 }
+
+func (e *Entry) NeckRingHealthWarning() error {
+	pastureList := e.FindPastureList()
+	if pastureList == nil || len(pastureList) == 0 {
+		return nil
+	}
+	for _, pasture := range pastureList {
+		if err := e.UpdateNeckRingHealth(pasture.Id); err != nil {
+			zaplog.Error("NeckRingHealthWarning", zap.Any("UpdateNeckRingHealth", err), zap.Any("pasture", pasture))
+		}
+	}
+	return nil
+}
+
+func (e *Entry) UpdateNeckRingHealth(pastureId int64) error {
+	neckRingConfigureList, err := e.GetSystemNeckRingConfigure(pastureId)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	healthValue := int32(0)
+	for _, v := range neckRingConfigureList {
+		if v.Name != model.HealthWarning {
+			continue
+		}
+		healthValue = int32(v.Value)
+	}
+
+	nowTime := time.Now()
+	neckRingHealthList := make([]*model.NeckRingHealth, 0)
+	if err = e.DB.Model(new(model.NeckRingHealth)).
+		Select("MAX(id) AS id,neck_ring_number,cow_id,score,max_high,created_at,min_high,min_chew,min_intake,sum_chew,before_three_sum_chew").
+		Where("pasture_id = ?", pastureId).
+		Where("heat_date >= ?", nowTime.AddDate(0, 0, -1).Format(model.LayoutDate2)).
+		Group("neck_ring_number").
+		Find(&neckRingHealthList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	newNeckRingHealthWarningList := make([]*model.NeckRingHealthWarning, 0)
+	for _, v := range neckRingHealthList {
+		newScore := calculateNewScore(v)
+		if newScore > healthValue {
+			continue
+		}
+		cowInfo, err := e.GetCowById(v.CowId)
+		if err != nil {
+			continue
+		}
+		if cowInfo == nil {
+			continue
+		}
+
+		newNeckRingHealthWarning := model.NewNeckRingHealthWarning(pastureId, v, cowInfo, newScore)
+		newNeckRingHealthWarningList = append(newNeckRingHealthWarningList, newNeckRingHealthWarning)
+	}
+
+	if len(newNeckRingHealthWarningList) > 0 {
+		if err = e.DB.Model(new(model.NeckRingHealthWarning)).
+			Create(&newNeckRingHealthWarningList).Error; err != nil {
+			zaplog.Error("UpdateNeckRingHealth", zap.Any("error", err), zap.Any("newNeckRingHealthWarningList", newNeckRingHealthWarningList))
+		}
+	}
+	return nil
+}
+
+func (e *Entry) FindHealthWarning(pastureId int64, data *model.NeckRingHealth) int64 {
+	var count int64
+	if err := e.DB.Model(new(model.NeckRingHealth)).
+		Where("pasture_id = ?", pastureId).
+		Where("cow_id = ?", data.CowId).
+		Where("heat_date = ?", data.HeatDate).
+		Where("score = ?", data.Score).
+		Count(&count).Error; err != nil {
+		zaplog.Error("FindHealthWarning", zap.Any("error", err))
+	}
+	return count
+}
+
+func calculateNewScore(data *model.NeckRingHealth) int32 {
+	return data.Score
+}

+ 2 - 1
module/crontab/interface.go

@@ -39,5 +39,6 @@ type Crontab interface {
 	NeckRingOriginalMerge() error // 合并脖环数据
 	NeckRingCalculate() error     // 更新脖环数据
 	UpdateCowEstrus() error       // 获取牛只疑似发情数据
-	UpdateNeckRingWarning() error // 发情和健康预警
+	NeckRingEstrusWarning() error // 发情预警
+	NeckRingHealthWarning() error // 健康预警
 }

+ 91 - 91
module/crontab/neck_ring_calculate.go

@@ -3,6 +3,7 @@ package crontab
 import (
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"math"
 	"time"
 
@@ -41,11 +42,17 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 	if err != nil {
 		return xerr.WithStack(err)
 	}
+
+	// 未配置的滤波数据不参与计算
+	if xToday == nil {
+		return nil
+	}
+
 	var processIds []int64
 	// 更新活动滤波
 	processIds, err = e.FirstFilterUpdate(pastureId, xToday)
 	if err != nil {
-		zaplog.Error("NeckRingCalculate", zap.Any("FirstFilterUpdate", err), zap.Any("xToday", xToday))
+		zaplog.Error("NeckRingCalculate", zap.Any("pastureId", pastureId), zap.Any("FirstFilterUpdate", err), zap.Any("xToday", xToday))
 	}
 
 	zaplog.Info("NeckRingCalculate", zap.Any("xToday", xToday), zap.Any("processIds", processIds))
@@ -56,7 +63,7 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 	e.WeeklyUpdateActiveHabit(pastureId, processIds, xToday)
 
 	// 二次更新滤波
-	e.SecondUpdateChangeFilter(pastureId, xToday)
+	e.SecondUpdateChangeFilter(pastureId, processIds, xToday)
 
 	// 活动量校正系数和健康评分
 	e.FilterCorrectAndScoreUpdate(pastureId, processIds, xToday)
@@ -80,16 +87,18 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 
 // FirstFilterUpdate 首次更新活动滤波
 func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds []int64, err error) {
+	limit := e.Cfg.NeckRingLimit
+	if limit <= 0 {
+		limit = defaultLimit
+	}
 	newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
 	if err = e.DB.Model(new(model.NeckActiveHabit)).
-		Where("heat_date >= ?", "2025-03-01").
+		Where("heat_date >= ?", time.Now().AddDate(0, 0, -1).Format(model.LayoutDate2)).
 		Where("pasture_id = ?", pastureId).
 		Where("is_show = ?", pasturePb.IsShow_No).
-		//Where("record_count = ?", model.DefaultRecordCount).
 		Where(e.DB.Where("high >= ?", xToDay.High).Or("rumina >= ?", xToDay.Rumina)).
-		Where("cow_id > ?", 0).
 		Order("heat_date,neck_ring_number,frameid").
-		Limit(int(defaultLimit)).
+		Limit(int(limit)).
 		Find(&newNeckActiveHabitList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -110,8 +119,8 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 		}
 
 		// 8小时数据不全的不参与滤波
-		activeTime, _ := time.Parse(model.LayoutTime, v.ActiveTime)
-		if v.RecordCount != model.DefaultRecordCount && activeTime.Sub(time.Now()).Hours() < 8 {
+		activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
+		if v.RecordCount != model.DefaultRecordCount && time.Now().Sub(activeTime).Hours() < 8 {
 			continue
 		}
 
@@ -119,7 +128,7 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 		heatDate := v.HeatDate
 		if v.Frameid == 0 {
 			frameId = 11
-			heatDateParse, _ := time.Parse(model.LayoutDate2, heatDate)
+			heatDateParse, _ := util.TimeParseLocal(model.LayoutDate2, heatDate)
 			heatDate = heatDateParse.AddDate(0, 0, -1).Format(model.LayoutDate2)
 		} else {
 			frameId -= 1
@@ -182,17 +191,15 @@ func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds [
 }
 
 // SecondUpdateChangeFilter 第二次更新变化趋势滤波
-func (e *Entry) SecondUpdateChangeFilter(pastureId int64, xToday *XToday) {
+func (e *Entry) SecondUpdateChangeFilter(pastureId int64, processIds []int64, xToday *XToday) {
 	newChangeFilterList := make([]*ChangeFilterData, 0)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
-		Select("id", "neck_ring_number", "change_high", "change_filter", "rumina_filter", "change_rumina",
-			"chew_filter", "change_chew", "heat_date", "frameid", "IF(lact = 0, 0.8, 1) as xlc_dis_count").
-		Where("heat_date >= ?", time.Now().AddDate(0, 0, -2).Format(model.LayoutDate2)).
+		Select("id", "neck_ring_number", "change_high", "change_filter", "rumina_filter", "change_rumina", "chew_filter", "change_chew", "heat_date", "frameid", "IF(lact = 0, 0.8, 1) as xlc_dis_count").
 		Where("pasture_id = ?", pastureId).
+		Where("id IN (?)", processIds).
 		Where("change_filter = ?", model.InitChangeFilter).
 		Where("change_high > ?", MinChangeHigh).
 		Order("neck_ring_number,heat_date,frameid").
-		Limit(int(defaultLimit)).
 		Find(&newChangeFilterList).Error; err != nil {
 		zaplog.Error("SecondUpdateChangeFilter", zap.Any("error", err), zap.Any("xToday", xToday))
 		return
@@ -203,63 +210,52 @@ func (e *Entry) SecondUpdateChangeFilter(pastureId int64, xToday *XToday) {
 		heatDate := v.HeatDate
 		if v.FrameId == 0 {
 			frameId = 11
-			heatDateParse, _ := time.Parse(model.LayoutDate2, heatDate)
+			heatDateParse, _ := util.TimeParseLocal(model.LayoutDate2, heatDate)
 			heatDate = heatDateParse.AddDate(0, 0, -1).Format(model.LayoutDate2)
 		} else {
 			frameId -= 1
 		}
 
+		xChangeDiscount := float64(xToday.XChangeDiscount) / 10
+		xRuminaDisc := float64(xToday.XRuminaDisc) / 10
 		secondFilterData := e.FindFilterData(pastureId, v.NeckRingNumber, heatDate, frameId)
-		if v.ChangeFilter > MinChangeFilter {
-			secondFilterData.ChangeFilter = v.ChangeFilter
-		} else {
-			if v.NeckRingNumber == secondFilterData.NeckRingNumber {
-				changeFilter := float64(secondFilterData.ChangeFilter)*(1-(float64(xToday.XChangeDiscount)/10)*v.XlcDisCount) +
-					math.Min(float64(v.ChangeHigh), float64(secondFilterData.ChangeFilter)+135)*(float64(xToday.XChangeDiscount)/10)*v.XlcDisCount
-				secondFilterData.ChangeFilter = int32(changeFilter)
-			} else {
-				secondFilterData.ChangeFilter = 0
-			}
+		if secondFilterData.ChangeFilter <= MinChangeFilter {
+			secondFilterData.ChangeFilter = 0
 		}
 
-		if v.RuminaFilter > MinRuminaFilter {
-			secondFilterData.RuminaFilter = v.ChangeFilter
-		} else {
-			if v.NeckRingNumber == secondFilterData.NeckRingNumber {
-				discount := float64(xToday.XRuminaDisc) / 10 * v.XlcDisCount
-				if math.Abs(float64(v.ChangeRumina)) > 60 {
-					discount *= 0.5
-				}
-				secondFilterData.RuminaFilter = int32(float64(secondFilterData.RuminaFilter)*(1-discount) + float64(v.ChangeRumina)*discount)
-			} else {
-				secondFilterData.RuminaFilter = 0
-			}
+		if secondFilterData.RuminaFilter <= MinRuminaFilter {
+			secondFilterData.RuminaFilter = 0
 		}
 
-		secondFilterData.RuminaFilter = int32(math.Min(50, float64(secondFilterData.RuminaFilter)))
+		if secondFilterData.ChewFilter <= MinChewFilter {
+			secondFilterData.ChewFilter = 0
+		}
 
-		if v.ChewFilter > MinChewFilter {
-			secondFilterData.ChewFilter = v.ChangeChew
-		} else {
-			if v.NeckRingNumber == secondFilterData.NeckRingNumber {
-				discount := float64(xToday.XRuminaDisc) / 10
-				if math.Abs(float64(v.ChangeChew)) > 60 {
-					discount *= 0.5
-				}
-				secondFilterData.ChewFilter = int32(float64(secondFilterData.ChewFilter)*(1-discount) + float64(v.ChangeChew)*discount)
-			} else {
-				secondFilterData.ChewFilter = 0
-			}
+		changeFilter := float64(v.ChangeFilter)
+		if v.ChangeFilter <= MinChangeFilter {
+			changeFilter = float64(secondFilterData.ChangeFilter)*(1-xChangeDiscount*v.XlcDisCount) + math.Min(float64(v.ChangeHigh), float64(secondFilterData.ChangeFilter)+135)*xChangeDiscount*v.XlcDisCount
+		}
+
+		ruminaFilter := float64(v.RuminaFilter)
+		discount := xRuminaDisc * v.XlcDisCount
+		if math.Abs(float64(v.ChangeRumina)) > 60 {
+			discount *= 0.5
 		}
+		ruminaFilter = float64(secondFilterData.RuminaFilter)*(1-discount) + float64(v.ChangeRumina)*discount
 
-		secondFilterData.ChewFilter = int32(math.Min(50, float64(secondFilterData.ChewFilter)))
+		chewFilter := float64(v.ChewFilter)
+		chewFilterDiscount := float64(1)
+		if math.Abs(float64(v.ChangeChew)) > 60 {
+			chewFilterDiscount = 0.5
+		}
+		chewFilter = float64(secondFilterData.ChewFilter)*(1-xRuminaDisc*chewFilterDiscount) + float64(v.ChangeChew)*xRuminaDisc*chewFilterDiscount
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
 			Select("change_filter", "rumina_filter", "chew_filter").
 			Where("id = ?", v.Id).
 			Updates(map[string]interface{}{
-				"change_filter": secondFilterData.ChangeFilter,
-				"rumina_filter": secondFilterData.RuminaFilter,
-				"chew_filter":   secondFilterData.ChewFilter,
+				"change_filter": int32(changeFilter),
+				"rumina_filter": int32(math.Min(50, ruminaFilter)),
+				"chew_filter":   int32(math.Min(50, chewFilter)),
 			}).Error; err != nil {
 			zaplog.Error("SecondUpdateChangeFilter", zap.Any("error", err), zap.Any("secondFilterData", secondFilterData))
 		}
@@ -291,7 +287,7 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64,
 			Where(e.DB.Where("high > ?", xToday.High).Or("rumina >= ?", xToday.Rumina)).
 			Where("active_time <= ?", beginDayDate.Add(-12*time.Hour).Format(model.LayoutTime)).
 			Where("change_filter > ?", MinChangeFilter).
-			Where("neck_ring_number = ?").
+			Where("neck_ring_number = ?", v.NeckRingNumber).
 			Having("nb > ?", DefaultNb).
 			First(&activityVolume).Error; err != nil {
 			zaplog.Error("ActivityVolumeChanges-0", zap.Any("error", err), zap.Any("xToday", xToday))
@@ -349,43 +345,33 @@ func (e *Entry) UpdateFilterCorrect(pastureId int64, processIds []int64) {
 
 // UpdateChangeAdJust 更新群体校正数据
 func (e *Entry) UpdateChangeAdJust(pastureId int64, xToday *XToday) {
-	/*-- 插入群体校正表
-	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())).
-		Select("h.neck_ring_number,h.heat_date, h.frameid, c.pen_id, c.pen_name, COUNT(*) as nb, ROUND(AVG(h.change_high)) as change_high, ROUND(AVG(h.change_filter)) as change_filter").
+		Select(`h.neck_ring_number,h.heat_date, h.frameid, c.pen_id, c.pen_name, COUNT(*) as nb,
+		ROUND(AVG(h.change_high)) as change_high, ROUND(AVG(h.change_filter)) as change_filter`).
 		Joins("JOIN cow as c ON h.neck_ring_number = c.neck_ring_number").
 		Where("h.pasture_id = ?", pastureId).
 		Where("h.heat_date >= ?", oneDayAgo).
+		Where("h.is_show = ?", pasturePb.IsShow_Ok).
 		Where("h.cow_id >= ?", 0).
+		Where("c.pen_id > ?", 0).
 		Group("h.heat_date, h.frameid, c.pen_id").
-		Order("h.heat_date, h.frameid, c.pen_name").
+		Order("h.heat_date, h.frameid, c.pen_id").
 		Find(&res).Error; err != nil {
 		zaplog.Error("UpdateChangeAdJust", zap.Any("error", err), zap.Any("xToday", xToday))
 	}
 
-	// todo ABS(cg.changefilter)>=10;
 	for _, v := range res {
+		if math.Abs(float64(v.ChangeFilter)) < 10 {
+			continue
+		}
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
-			Where("id > ?", xToday.LastMaxHabitId).
+			Where("pasture_id = ?", pastureId).
+			Where("neck_ring_number = ?", v.NeckRingNumber).
 			Where("heat_date = ?", v.HeatDate).
 			Where("frameid = ?", v.FrameId).
-			Where("neck_ring_number = ?", v.NeckRingNumber).
-			Update("change_adjust", v.ChangeHigh).Error; err != nil {
+			Update("change_adjust", v.ChangeFilter).Error; err != nil {
 			zaplog.Error("UpdateChangeAdJust-1", zap.Any("error", err), zap.Any("xToday", xToday))
 		}
 	}
@@ -406,6 +392,11 @@ func (e *Entry) XToday(pastureId int64) (*XToday, error) {
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
+
+	if len(systemConfigureList) <= 0 {
+		return nil, nil
+	}
+
 	for _, v := range systemConfigureList {
 		switch v.Name {
 		case model.MaxHabit:
@@ -499,6 +490,7 @@ func (e *Entry) SumUpdateActiveHabit(pastureId int64, newNeckActiveHabitList []*
 func (e *Entry) ActiveChange(pastureId int64, processIds []int64, xToDay *XToday) {
 	newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
+		Where("pasture_id = ?", pastureId).
 		Where("id IN (?)", processIds).
 		Where("high_habit > ?", 0).
 		Where(e.DB.Where("high >= ?", xToDay.High).Or("rumina >= ?", xToDay.Rumina)).
@@ -507,20 +499,16 @@ func (e *Entry) ActiveChange(pastureId int64, processIds []int64, xToDay *XToday
 	}
 
 	for _, v := range newNeckActiveHabitList {
-		highDiff := v.FilterHigh - v.HighHabit
-		denominator := float64(v.WeekHigh)*0.6 + float64(v.HighHabit)*0.2 + float64(xToDay.WeeklyActive)*0.2
-		if highDiff > 0 {
-			v.ChangeHigh = int32(math.Round((float64(highDiff) / denominator / float64(v.HighHabit)) * 100))
-		} else {
-			v.ChangeHigh = int32(math.Round(float64(highDiff) / float64(v.HighHabit) * 100))
-		}
+		changeHigh := calculateChangeHigh(v, xToDay.WeeklyActive)
+		changeRumina := int32(0)
+		changeChew := int32(0)
 
 		if v.RuminaHabit != 0 {
-			v.ChangeRumina = int32(math.Round(float64(v.FilterRumina-v.RuminaHabit) / float64(v.RuminaHabit) * 100))
+			changeRumina = int32(math.Round(float64(v.FilterRumina-v.RuminaHabit) / float64(v.RuminaHabit) * 100))
 		}
 
 		if v.ChewHabit != 0 {
-			v.ChangeChew = int32(math.Round(float64(v.FilterChew-v.ChewHabit) / float64(v.ChewHabit) * 100))
+			changeChew = int32(math.Round(float64(v.FilterChew-v.ChewHabit) / float64(v.ChewHabit) * 100))
 		}
 
 		// 更新过滤值
@@ -528,14 +516,13 @@ func (e *Entry) ActiveChange(pastureId int64, processIds []int64, xToDay *XToday
 			Select("change_high", "change_rumina", "change_chew").
 			Where("id = ?", v.Id).
 			Updates(map[string]interface{}{
-				"change_high":   v.ChangeHigh,
-				"change_rumina": v.ChangeRumina,
-				"change_chew":   v.ChangeChew,
+				"change_high":   changeHigh,
+				"change_rumina": changeRumina,
+				"change_chew":   changeChew,
 			}).Error; err != nil {
-			zaplog.Error("WeeklyUpdateActiveHabit",
-				zap.Error(err),
+			zaplog.Error("ActiveChange",
+				zap.Any("err", err),
 				zap.Any("NeckActiveHabit", v),
-				zap.Any("pastureId", pastureId),
 			)
 		}
 	}
@@ -571,3 +558,16 @@ func (e *Entry) Before3DaysNeckActiveHabit(pastureId int64, processIds []int64)
 		}
 	}
 }
+
+// calculateChangeHigh 计算活动量变化
+func calculateChangeHigh(data *model.NeckActiveHabit, weeklyActive int32) int32 {
+	highDiff := data.FilterHigh - data.HighHabit
+	changeHigh := int32(0)
+	if highDiff > 0 {
+		denominator := float64(data.WeekHigh)*0.6 + float64(data.HighHabit)*0.2 + float64(weeklyActive)*0.2
+		changeHigh = int32(math.Round((float64(highDiff) / denominator) * 100))
+	} else {
+		changeHigh = int32(math.Round(float64(highDiff) / float64(data.HighHabit) * 100))
+	}
+	return changeHigh
+}

+ 25 - 26
module/crontab/neck_ring_estrus.go

@@ -3,6 +3,7 @@ package crontab
 import (
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
@@ -42,6 +43,13 @@ func (e *Entry) UpdateCowEstrus() (err error) {
 func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
 	xToday := &XToday{}
 	systemConfigureList, err := e.GetSystemNeckRingConfigure(pastureId)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	if systemConfigureList == nil || len(systemConfigureList) == 0 {
+		return nil
+	}
+
 	for _, v := range systemConfigureList {
 		switch v.Name {
 		case model.ActiveLow:
@@ -52,27 +60,16 @@ func (e *Entry) EntryCowEstrus(pastureId int64) (err error) {
 			xToday.ActiveHigh = int32(v.Value)
 		}
 	}
-
-	if err = e.CowEstrusWarning(pastureId, xToday); err != nil {
-		zaplog.Error("EntryCowEstrus", zap.Any("CowEstrusWarning", err), zap.Any("xToday", xToday))
-	}
-
-	// 将3天前历史发情预警数据更新为已过期
-	if err = e.DB.Model(new(model.NeckRingEstrus)).
-		Where("first_time != ?", "").
-		Where("first_time <= ?", time.Now().AddDate(0, 0, -3).Format(model.LayoutTime)).
-		Update("check_result", pasturePb.CheckResult_Overdue).Error; err != nil {
-		zaplog.Error("EntryCowEstrus", zap.Any("UpdateEventEstrus", err))
-	}
-
+	nowTime := time.Now()
+	e.CowEstrusWarning(pastureId, xToday, nowTime)
+	e.UpdateNewNeckRingEstrus(pastureId, nowTime)
 	return nil
 }
 
 // CowEstrusWarning 发情预警
-func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
-	nowTime := time.Now()
+func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday, nowTime time.Time) {
 	neckActiveHabitList := make([]*model.NeckActiveHabit, 0) // todo 需要考虑到数据量太大的情况
-	if err = e.DB.Model(new(model.NeckActiveHabit)).
+	if err := e.DB.Model(new(model.NeckActiveHabit)).
 		Where("heat_date = ?", nowTime.Format(model.LayoutDate2)).
 		Where("pasture_id = ?", pastureId).
 		Where("filter_high > 0 AND change_filter > ?", model.DefaultChangeFilter).
@@ -80,7 +77,8 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 		Where(e.DB.Where("calving_age >= ?", MinCalvingAge).Or("lact = ?", MinLact)). // 排除产后20天内的发情牛
 		Order("cow_id").
 		Find(&neckActiveHabitList).Error; err != nil {
-		return xerr.WithStack(err)
+		zaplog.Error("CowEstrusWarning", zap.Any("Find", err))
+		return
 	}
 
 	zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitList", neckActiveHabitList))
@@ -90,6 +88,8 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 		if cft < float32(xToday.ActiveLow-XAdjust21) {
 			continue
 		}
+
+		zaplog.Info("CowEstrusWarning", zap.Any("cft", cft), zap.Any("habit", habit))
 		if neckActiveHabitMap[habit.CowId] == nil {
 			neckActiveHabitMap[habit.CowId] = make([]*model.NeckActiveHabit, 0)
 		}
@@ -97,14 +97,13 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 	}
 
 	zaplog.Info("CowEstrusWarning", zap.Any("neckActiveHabitMap", neckActiveHabitMap))
-
 	neckRingEstrusList := make([]*model.NeckRingEstrus, 0)
 	for cowId, cowHabitList := range neckActiveHabitMap {
 		// 最近3天最大发情记录,小于该变化趋势的不再插入
 		before3Data := e.GetBeforeThreeDaysCowEstrus(cowId, nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
 		// 判断最近50天内是否存在发情记录(发情等级>=2),如果18~25天@xadjust21,如果36~50天@xadjust42
 		cowEstrus := e.GetTwoEstrus(pastureId, cowId, nowTime.AddDate(0, 0, -100).Format(model.LayoutTime), nowTime.AddDate(0, 0, -2).Format(model.LayoutTime))
-		activeDateTime, _ := time.Parse(model.LayoutTime, cowEstrus.ActiveDate)
+		activeDateTime, _ := util.TimeParseLocal(model.LayoutTime, cowEstrus.ActiveDate)
 		if activeDateTime.Unix() >= nowTime.AddDate(0, 0, -25).Unix() && activeDateTime.Unix() <= nowTime.AddDate(0, 0, -18).Unix() {
 			cowEstrus.HadJust = XAdjust21
 		}
@@ -124,18 +123,17 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 				maxHigh = habit.FilterHigh
 			}
 			// 获取最新的 CreateTime
-			activeTimeParse, _ := time.Parse(model.LayoutTime, habit.ActiveTime)
+			activeTimeParse, _ := util.TimeParseLocal(model.LayoutTime, habit.ActiveTime)
 			if activeTimeParse.After(lastActiveDate) {
 				lastActiveDate = activeTimeParse
 			}
 		}
 
 		b48 := float64(0)
-		t3, _ := time.Parse(model.LayoutTime, before3Data.ActiveTime)
+		t3, _ := util.TimeParseLocal(model.LayoutTime, before3Data.ActiveTime)
 		b48 = t3.Sub(lastActiveDate).Hours()
 
 		if (int32(maxCft) > before3Data.DayHigh || before3Data.CowId == 0 || b48 > B48) && int32(maxCft)+cowEstrus.HadJust > xToday.ActiveLow {
-
 			level := calculateActiveLevel(maxCft, cowEstrus, xToday)
 			cowInfo := e.FindCowInfoByCowId(cowId)
 			if cowInfo == nil {
@@ -178,17 +176,18 @@ func (e *Entry) CowEstrusWarning(pastureId int64, xToday *XToday) (err error) {
 
 	zaplog.Info("CowEstrusWarning", zap.Any("neckRingEstrusList", neckRingEstrusList))
 	if len(neckRingEstrusList) > 0 {
-		if err = e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
+		if err := e.DB.Model(new(model.NeckRingEstrus)).Create(neckRingEstrusList).Error; err != nil {
 			zaplog.Error("CowEstrusWarningNew", zap.Any("eventEstrusList", neckRingEstrusList), zap.Any("err", err))
 		}
 	}
+}
 
+func (e *Entry) UpdateNewNeckRingEstrus(pastureId int64, nowTime time.Time) {
 	// 更新牛只首次发情时间
 	e.UpdateEstrusFirstTime1(pastureId, nowTime)
 	e.UpdateEstrusIsPeak(pastureId, nowTime)
 	e.UpdateEstrusFirstTime2(pastureId, nowTime)
 	e.UpdateEstrusFirstTime3(pastureId, nowTime)
-	return nil
 }
 
 // UpdateEstrusFirstTime1 更新牛只首次发情时间
@@ -220,7 +219,7 @@ func (e *Entry) UpdateEstrusFirstTime2(pastureId int64, xToday time.Time) {
 func (e *Entry) UpdateEstrusFirstTime3(pastureId int64, xToday time.Time) {
 	neckRingEstrusList := e.FindNeckRingEstrusByFirstTimeEmpty(pastureId)
 	for _, v := range neckRingEstrusList {
-		activeTime, _ := time.Parse(model.LayoutTime, v.ActiveTime)
+		activeTime, _ := util.TimeParseLocal(model.LayoutTime, v.ActiveTime)
 		if activeTime.After(xToday.AddDate(0, 0, -2)) {
 			if err := e.DB.Model(new(model.NeckRingEstrus)).
 				Where("id = ?", v.Id).
@@ -243,7 +242,7 @@ func (e *Entry) UpdateEstrusIsPeak(pastureId int64, xToday time.Time) {
 	}
 
 	for _, estrus := range neckRingEstrusList {
-		activeTime, _ := time.Parse(model.LayoutTime, estrus.ActiveTime)
+		activeTime, _ := util.TimeParseLocal(model.LayoutTime, estrus.ActiveTime)
 		if activeTime.Before(xToday) || activeTime.After(xToday.AddDate(0, 0, 1)) {
 			continue
 		}

+ 15 - 0
module/crontab/neck_ring_estus_test.go

@@ -6096,3 +6096,18 @@ func TestRecalculate(t *testing.T) {
 	b, _ := json.Marshal(data)
 	fmt.Println(string(b))
 }
+
+func TestIndex(t *testing.T) {
+	ActiveTime := "2025-03-13 13:00:00"
+	loc, _ := time.LoadLocation("Local")
+	activeTime, _ := time.ParseInLocation(model.LayoutTime, ActiveTime, loc)
+	fmt.Println("activeTime", activeTime.Unix())
+	for i := 0; i < 7; i++ {
+		hours := time.Now().Sub(activeTime).Hours()
+		if i != model.DefaultRecordCount && int64(hours) < 8 {
+			fmt.Println("i", i, "hours", int64(hours))
+		} else {
+			fmt.Println("j", i, "hours", int64(hours))
+		}
+	}
+}

+ 1 - 1
module/crontab/neck_ring_merge.go

@@ -178,7 +178,7 @@ func Recalculate(neckRingList []*model.NeckRingOriginal) []*model.NeckActiveHabi
 		if v.RecordCount < model.DefaultRecordCount {
 			currMaxXframeId := util.FrameIdMapReverse[int32(currTime.Hour())]
 			activeDateString := fmt.Sprintf("%s %02d:00:00", v.ActiveDate, v.XframeId*2+1)
-			activeDate, _ := time.Parse(model.LayoutTime, activeDateString)
+			activeDate, _ := util.TimeParseLocal(model.LayoutTime, activeDateString)
 			if currMaxXframeId-v.XframeId <= 1 && currTime.Add(-1*time.Hour).Unix() < activeDate.Unix() {
 				delete(originalMapData, k)
 				continue

+ 7 - 11
module/crontab/sql.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"kpt-pasture/model"
+	"kpt-pasture/util"
 	"time"
 
 	pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
@@ -181,18 +182,13 @@ func (e *Entry) FindFilterData(pastureId int64, neckRingNumber, heatDate string,
 		Where("frameid = ?", frameId).
 		Where("pasture_id = ?", pastureId).
 		First(firstFilterData).Error; err != nil {
-		zaplog.Error("FirstFilterUpdate",
-			zap.Any("err", err),
-			zap.Any("NeckRingNumber", neckRingNumber),
-			zap.Any("heatDate", heatDate),
-			zap.Any("frameId", frameId),
-		)
+		zaplog.Error("FirstFilterUpdate", zap.Any("err", err), zap.Any("NeckRingNumber", neckRingNumber), zap.Any("heatDate", heatDate), zap.Any("frameId", frameId))
 	}
 	return firstFilterData
 }
 
 func (e *Entry) FindWeekHabitData(pastureId int64, neckRingNumber, heatDate string, frameid int32, xToday *XToday) *WeekHabit {
-	beginDayDate, _ := time.Parse(model.LayoutDate2, heatDate)
+	beginDayDate, _ := util.TimeParseLocal(model.LayoutDate2, heatDate)
 	before7DayDate := beginDayDate.AddDate(0, 0, -7).Format(model.LayoutDate2)
 	before1DayDate := beginDayDate.AddDate(0, 0, -1).Format(model.LayoutDate2)
 	weekHabitData := &WeekHabit{}
@@ -224,10 +220,10 @@ func (e *Entry) FindWeekHabitData(pastureId int64, neckRingNumber, heatDate stri
 }
 
 func (e *Entry) FindSumHabitData(pastureId int64, neckRingNumber, heatDate string, frameid int32, xToday *XToday) *SumHabit {
-	beginDayDate, _ := time.Parse(model.LayoutDate2, heatDate)
+	beginDayDate, _ := util.TimeParseLocal(model.LayoutDate2, heatDate)
 	before1DayDate := beginDayDate.AddDate(0, 0, -1).Format(model.LayoutDate2)
 	activeTime := fmt.Sprintf("%s %02d:00:00", heatDate, frameid*2+1)
-	activeStartTimeParse, _ := time.Parse(model.LayoutTime, activeTime)
+	activeStartTimeParse, _ := util.TimeParseLocal(model.LayoutTime, activeTime)
 	activeStartTime := activeStartTimeParse.Add(-23 * time.Hour).Format(model.LayoutTime)
 	// 累计24小时数值
 	sumHabitData := &SumHabit{}
@@ -239,7 +235,7 @@ func (e *Entry) FindSumHabitData(pastureId int64, neckRingNumber, heatDate strin
 			"IF(COUNT(1)>6, ROUND(AVG(inactive)*12,0), 0) as sum_inactive",
 			"IF(COUNT(1)>6, ROUND(AVG(active)*12,0), 0) as sum_active",
 			"MAX(change_filter) as sum_max_high",
-			fmt.Sprintf("MIN(IF(change_filter > %d, change_filter, %d)) as sum_min_high", model.DefaultChangeFilter, model.InitChangeFilter),
+			fmt.Sprintf("MIN(IF(change_filter > %d, change_filter, 0)) as sum_min_high", model.DefaultChangeFilter),
 			fmt.Sprintf("MIN( CASE WHEN filter_chew > %d THEN filter_chew WHEN filter_rumina >= %d THEN filter_rumina ELSE 0 END) as sum_min_chew", model.DefaultChangeFilter, model.DefaultRuminaFilter),
 		).
 		Where("pasture_id = ?", pastureId).
@@ -261,7 +257,7 @@ func (e *Entry) FindSumHabitData(pastureId int64, neckRingNumber, heatDate strin
 
 func (e *Entry) FindBefore3DaysNeckActiveHabit(pastureId int64, neckRingNumber, heatDate string, frameid int32) *model.NeckActiveHabit {
 	before3DaysNeckActiveHabit := &model.NeckActiveHabit{}
-	beginDayDate, _ := time.Parse(model.LayoutDate2, heatDate)
+	beginDayDate, _ := util.TimeParseLocal(model.LayoutDate2, heatDate)
 	before3DayDate := beginDayDate.AddDate(0, 0, -3).Format(model.LayoutDate2)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
 		Select("sum_rumina", "sum_intake").

+ 21 - 22
util/util.go

@@ -95,8 +95,7 @@ func TimeParseLocalUnix(DayTime string) int64 {
 		value = fmt.Sprintf("%s 00:00:00", DayTime)
 	}
 
-	loc, _ := time.LoadLocation("Local")
-	theTime, _ := time.ParseInLocation(LayoutTime, value, loc)
+	theTime, _ := TimeParseLocal(LayoutTime, value)
 	return theTime.Unix()
 }
 
@@ -108,20 +107,15 @@ func TimeParseLocalEndUnix(DayTime string) int64 {
 		value = fmt.Sprintf("%s 23:59:59", DayTime)
 	}
 
-	loc, _ := time.LoadLocation("Local")
-	theTime, _ := time.ParseInLocation(LayoutTime, value, loc)
+	theTime, _ := TimeParseLocal(LayoutTime, value)
 	return theTime.Unix()
 }
 
 // ConvertParseLocalUnix 字符串转换当天时间戳
 // eg 15:04:05 => 1676998245
 func ConvertParseLocalUnix(timeParse string) (int64, error) {
-	loc, err := time.LoadLocation("Local")
-	if err != nil {
-		return 0, err
-	}
 	value := fmt.Sprintf("%s %s", time.Now().Format(Layout), timeParse)
-	theTime, err := time.ParseInLocation(LayoutTime, value, loc)
+	theTime, err := TimeParseLocal(LayoutTime, value)
 	if err != nil {
 		return 0, err
 	}
@@ -152,7 +146,7 @@ func Ceil(x float64) float64 {
 // 接受一个字符串形式的月份(如 "2024-12"),
 // 并返回该月份的最后一天的日期(2024-12-31)
 func GetLastDayOfMonth(month string) (string, error) {
-	t, err := time.Parse(LayoutMonth, month)
+	t, err := TimeParseLocal(LayoutMonth, month)
 	if err != nil {
 		return "", err // 如果解析失败,返回错误
 	}
@@ -170,13 +164,13 @@ func GetLastDayOfMonth(month string) (string, error) {
 // 并返回一个包含这两个月份之间所有月份的字符串切片。
 func GetMonthsInRange(startMonth, endMonth string) ([]string, error) {
 	// 解析起始月份
-	startTime, err := time.Parse(LayoutMonth, startMonth)
+	startTime, err := TimeParseLocal(LayoutMonth, startMonth)
 	if err != nil {
 		return nil, err
 	}
 
 	// 解析结束月份
-	endTime, err := time.Parse(LayoutMonth, endMonth)
+	endTime, err := TimeParseLocal(LayoutMonth, endMonth)
 	if err != nil {
 		return nil, err
 	}
@@ -202,12 +196,12 @@ func RoundToTwoDecimals(num float64) float64 {
 // Get21DayPeriods 获取范围时间内21天周期的切片
 // 从结束时间开始往前推算,超过开始时间,继续向前推算,直至凑整21天
 func Get21DayPeriods(startDay, endDay string) ([][]string, error) {
-	startDate, err := time.Parse(Layout, startDay)
+	startDate, err := TimeParseLocal(Layout, startDay)
 	if err != nil {
 		return nil, err
 	}
 
-	endDate, err := time.Parse(Layout, endDay)
+	endDate, err := TimeParseLocal(Layout, endDay)
 	if err != nil {
 		return nil, err
 	}
@@ -250,7 +244,7 @@ func GetRangeDayMiddleDay(dateRange []string, middleDay int32) (string, error) {
 	if middleDay < 0 {
 		return "", xerr.Custom("middle day is not enough")
 	}
-	startDate, _ := time.Parse(Layout, dateRange[0])
+	startDate, _ := TimeParseLocal(Layout, dateRange[0])
 	return startDate.AddDate(0, 0, int(middleDay)-1).Format(Layout), nil
 }
 
@@ -261,11 +255,11 @@ func GetRangeDayByDays(startDay, endDay string, days int32) ([][]string, error)
 	if days <= 0 {
 		return res, nil
 	}
-	startDate, err := time.Parse(Layout, startDay)
+	startDate, err := TimeParseLocal(Layout, startDay)
 	if err != nil {
 		return nil, err
 	}
-	endDate, err := time.Parse(Layout, endDay)
+	endDate, err := TimeParseLocal(Layout, endDay)
 	if err != nil {
 		return nil, err
 	}
@@ -369,11 +363,11 @@ func DaysBetween(startDayUnix int64, endDayUnix int64) int64 {
 // 2024-10-01 ~ 2024-10-07 => [2024-10-01,2024-10-02,2024-10-03,2024-10-04,2024-10-05,2024-10-06,2024-10-07]
 func GetDaysBetween(startDate, endDate string) ([]string, error) {
 	// 解析日期
-	start, err := time.Parse(Layout, startDate)
+	start, err := TimeParseLocal(Layout, startDate)
 	if err != nil {
 		return nil, fmt.Errorf("解析开始日期失败: %v", err)
 	}
-	end, err := time.Parse(Layout, endDate)
+	end, err := TimeParseLocal(Layout, endDate)
 	if err != nil {
 		return nil, fmt.Errorf("解析结束日期失败: %v", err)
 	}
@@ -406,11 +400,11 @@ func GetMonthsBetween(start, end string) ([]string, error) {
 	}
 
 	// 解析起始日期和结束日期
-	startTime, err := time.Parse(LayoutMonth, start)
+	startTime, err := TimeParseLocal(LayoutMonth, start)
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse start date: %v", err)
 	}
-	endTime, err := time.Parse(LayoutMonth, end)
+	endTime, err := TimeParseLocal(LayoutMonth, end)
 	if err != nil {
 		return nil, fmt.Errorf("failed to parse end date: %v", err)
 	}
@@ -434,7 +428,7 @@ func validateDate(date string) error {
 	if len(date) != 7 || date[4] != '-' {
 		return xerr.Custom("date format must be YYYY-MM")
 	}
-	_, err := time.Parse(LayoutMonth, date)
+	_, err := TimeParseLocal(LayoutMonth, date)
 	if err != nil {
 		return xerr.Customf("invalid date: %v", err)
 	}
@@ -603,3 +597,8 @@ func SubDays(startDay, endDay int64) int32 {
 	s2 := time.Unix(endDay, 0)
 	return int32(s2.Sub(s1).Hours() / 24)
 }
+
+func TimeParseLocal(layout, dateTime string) (time.Time, error) {
+	loc, _ := time.LoadLocation("Local")
+	return time.ParseInLocation(layout, dateTime, loc)
+}