Преглед изворни кода

dashboard: 撒料时间统计分析

Yi пре 1 година
родитељ
комит
1d9b996bed

+ 8 - 0
backend/operation/statistic.proto

@@ -250,4 +250,12 @@ message CommonValueRatio {
 
 message ValueRatio {
   repeated string value_ratio = 1;
+}
+
+// 首页 dashboard 撒料时间统计分析
+message SprinkleFeedTimeRequest {
+  int32 feed_formula_id = 1;    // 配方id
+  string start_date = 2;       // 开始时间
+  string end_date = 3;         // 结束时间
+  repeated int32 pasture_ids = 4;   //牧场ids
 }

+ 31 - 0
http/handler/dashboard/dashboard.go

@@ -21,6 +21,8 @@ func AnalysisAccuracy(c *gin.Context) {
 
 	if err := valid.ValidateStruct(&req,
 		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
@@ -44,6 +46,8 @@ func TopPasture(c *gin.Context) {
 
 	if err := valid.ValidateStruct(&req,
 		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
@@ -67,6 +71,8 @@ func ExecutionTime(c *gin.Context) {
 
 	if err := valid.ValidateStruct(&req,
 		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
@@ -79,3 +85,28 @@ func ExecutionTime(c *gin.Context) {
 	}
 	c.JSON(http.StatusOK, res)
 }
+
+// SprinkleFeedTime 撒料时间
+func SprinkleFeedTime(c *gin.Context) {
+	var req operationPb.SprinkleFeedTimeRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SprinkleFeedTime(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}

+ 1 - 0
http/route/app_api.go

@@ -126,6 +126,7 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		opsRoute.POST("/dashboard/accuracy", dashboard.AnalysisAccuracy)
 		opsRoute.POST("/dashboard/top_pasture", dashboard.TopPasture)
 		opsRoute.POST("/dashboard/exec_time", dashboard.ExecutionTime)
+		opsRoute.POST("/dashboard/sprinkle_time", dashboard.SprinkleFeedTime)
 	}
 }
 

+ 52 - 3
model/analysis_accuracy.go

@@ -92,9 +92,21 @@ type PastureTopData struct {
 }
 
 type ExecTimeResponse struct {
-	Code int32                    `json:"code"`
-	Msg  string                   `json:"msg"`
-	Data map[string]*ExecTimeData `json:"data"`
+	Code int32             `json:"code"`
+	Msg  string            `json:"msg"`
+	Data *ExecTimeDataList `json:"data"`
+}
+
+type ExecTimeDataList struct {
+	Chart     *ExecTimeDataListChart `json:"chart"`
+	TableList []map[string]string    `json:"table_list"`
+}
+
+type ExecTimeDataListChart struct {
+	Title        []string   `json:"title"`
+	AddFeedTime  [][]string `json:"add_feed_time"`
+	SprinkleTime [][]string `json:"sprinkle_time"`
+	StirTime     [][]string `json:"stir_time"`
 }
 
 type PastureExecTimeData struct {
@@ -116,3 +128,40 @@ type ExecTimeDetail struct {
 	DownMiddleValue string `json:"down_middle_value"`
 	UpMiddleValue   string `json:"up_middle_value"`
 }
+
+type SprinkleFeedTimeResponse struct {
+	Code int32                 `json:"code"`
+	Msg  string                `json:"msg"`
+	Data *SprinkleFeedTimeData `json:"data"`
+}
+
+type SprinkleFeedTimeData struct {
+	Chart     *SprinkleFeedTimeChart   `json:"chart"`
+	TableList []*SprinkleFeedTimeTable `json:"table_list"`
+}
+
+type SprinkleFeedTimeChart struct {
+	Title              []string  `json:"title"`
+	SprinkleNumberList [][]int32 `json:"sprinkle_number_list"`
+}
+
+type SprinkleFeedTimeTable struct {
+	PastureName             string `json:"pasture_name"`               // 牧场名称
+	BarnName                string `json:"barn_name"`                  // 栏舍名称
+	ClassNumber             string `json:"class_number"`               // 班次名称
+	RealitySprinkleFeedTime string `json:"reality_sprinkle_feed_time"` // 时间撒料时间
+}
+
+type PastureSprinkleStatisticsDataList struct {
+	Code int32                         `json:"code"`
+	Msg  string                        `json:"msg"`
+	Data []*SprinkleStatisticsDataList `json:"data"`
+}
+
+type SprinkleStatisticsDataList struct {
+	FBarId      int32  `json:"f_bar_id"`
+	FName       string `json:"f_name"`
+	InTime      string `json:"in_time"`
+	ProcessTime string `json:"process_time"`
+	Times       int32  `json:"times"`
+}

+ 1 - 1
model/pasture_data.go

@@ -12,7 +12,7 @@ type PastureResponse struct {
 }
 
 type DashboardAccuracyRequest struct {
-	CattleParentCategoryId int32  `json:"cattle_parent_category_id"`
+	CattleParentCategoryId int32  `json:"cattle_parent_category_id,omitempty"`
 	FeedFormulaId          int32  `json:"feed_formula_id"`
 	StartDate              string `json:"start_date"`
 	EndDate                string `json:"end_date"`

+ 157 - 6
module/backend/dashboard_service.go

@@ -12,11 +12,15 @@ import (
 	"net/http"
 	"sort"
 	"sync"
+	"time"
 
 	"go.uber.org/multierr"
 	"go.uber.org/zap"
 )
 
+const compareTime = 10 * 60
+
+// PasturePrefAnalysisData  PasturePrefExecTimeData PastureSprinkleFeedTime TODO  后期三个函数封装一下
 func (s *StoreEntry) PasturePrefAnalysisData(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (map[int64]*model.PastureAnalysisAccuracyData, error) {
 	groupPastureList, err := s.FindGroupPastureListByIds(ctx, req.PastureIds)
 	if err != nil {
@@ -101,6 +105,47 @@ func (s *StoreEntry) PasturePrefExecTimeData(ctx context.Context, req *operation
 	return res, nil
 }
 
+func (s *StoreEntry) PastureSprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (map[string][]*model.SprinkleStatisticsDataList, error) {
+	groupPastureList, err := s.FindGroupPastureListByIds(ctx, req.PastureIds)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	res := make(map[string][]*model.SprinkleStatisticsDataList, 0)
+	wg := sync.WaitGroup{}
+	wg.Add(len(groupPastureList))
+	var muError error
+	for _, pasture := range groupPastureList {
+		go func(p *model.GroupPasture) {
+			response := &model.PastureSprinkleStatisticsDataList{}
+			body := &model.DashboardAccuracyRequest{
+				PastureId:     int32(p.Id),
+				FeedFormulaId: req.FeedFormulaId,
+				StartDate:     req.StartDate,
+				EndDate:       req.EndDate,
+			}
+			if err = s.PastureHttpClient(ctx, DashboardSprinkleFeedTimeUrl, p.Id, body, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("PastureSprinkleFeedTime",
+					zap.String("url", DashboardSprinkleFeedTimeUrl),
+					zap.Any("pasture", p), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["PasturePrefExecTimeData"], DashboardSprinkleFeedTimeUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+			}
+			if response.Code != http.StatusOK {
+				muError = multierr.Append(muError, xerr.Custom(response.Msg))
+			}
+			res[p.Name] = response.Data
+			wg.Done()
+		}(pasture)
+	}
+	wg.Wait()
+	return res, nil
+}
+
 func (s *StoreEntry) SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error) {
 	res := &model.SearchAnalysisAccuracyResponse{
 		Code: http.StatusOK,
@@ -375,7 +420,15 @@ func (s *StoreEntry) ExecutionTime(ctx context.Context, req *operationPb.SearchA
 	res := &model.ExecTimeResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
-		Data: make(map[string]*model.ExecTimeData, 0),
+		Data: &model.ExecTimeDataList{
+			Chart: &model.ExecTimeDataListChart{
+				Title:        make([]string, 0),
+				AddFeedTime:  make([][]string, 0),
+				SprinkleTime: make([][]string, 0),
+				StirTime:     make([][]string, 0),
+			},
+			TableList: make([]map[string]string, 0),
+		},
 	}
 
 	pastureExecTime, err := s.PasturePrefExecTimeData(ctx, req)
@@ -384,18 +437,116 @@ func (s *StoreEntry) ExecutionTime(ctx context.Context, req *operationPb.SearchA
 	}
 
 	for pastureName, execTime := range pastureExecTime {
+		res.Data.Chart.Title = append(res.Data.Chart.Title, pastureName)
+		addFeedTimeStr, sprinkleTimeStr, stirTimeStr := make([]string, 0), make([]string, 0), make([]string, 0)
+		if execTime != nil {
+			addFeedTimeStr = append(addFeedTimeStr, execTime.AddFeedTime.MaxValue, execTime.AddFeedTime.UpMiddleValue,
+				execTime.AddFeedTime.MiddleValue, execTime.AddFeedTime.DownMiddleValue, execTime.AddFeedTime.MinValue)
+
+			sprinkleTimeStr = append(sprinkleTimeStr, execTime.SprinkleTime.MaxValue, execTime.SprinkleTime.UpMiddleValue,
+				execTime.SprinkleTime.MiddleValue, execTime.SprinkleTime.DownMiddleValue, execTime.SprinkleTime.MinValue)
+
+			stirTimeStr = append(stirTimeStr, execTime.StirTime.MaxValue, execTime.StirTime.UpMiddleValue,
+				execTime.StirTime.MiddleValue, execTime.StirTime.DownMiddleValue, execTime.StirTime.MinValue)
+		}
+
+		res.Data.Chart.AddFeedTime = append(res.Data.Chart.AddFeedTime, addFeedTimeStr)
+		res.Data.Chart.SprinkleTime = append(res.Data.Chart.SprinkleTime, sprinkleTimeStr)
+		res.Data.Chart.StirTime = append(res.Data.Chart.StirTime, stirTimeStr)
 		if execTime == nil {
 			continue
 		}
+		tableList := map[string]string{
+			"title":                           pastureName,
+			"add_feed_time":                   "加料时间",
+			"add_feed_time_max_value":         execTime.AddFeedTime.MaxValue,
+			"add_feed_time_up_middle_value":   execTime.AddFeedTime.UpMiddleValue,
+			"add_feed_time_middle_value":      execTime.AddFeedTime.MiddleValue,
+			"add_feed_time_down_middle_value": execTime.AddFeedTime.DownMiddleValue,
+			"add_feed_time_min_value":         execTime.AddFeedTime.MinValue,
+			"sprinkle_time":                   "撒料时间",
+			"sprinkle_time_max_value":         execTime.SprinkleTime.MaxValue,
+			"sprinkle_time_up_middle_value":   execTime.SprinkleTime.UpMiddleValue,
+			"sprinkle_time_middle_value":      execTime.SprinkleTime.MiddleValue,
+			"sprinkle_time_down_middle_value": execTime.SprinkleTime.DownMiddleValue,
+			"sprinkle_time_min_value":         execTime.SprinkleTime.MinValue,
+			"stir_time":                       "搅拌延迟时间",
+			"stir_time_max_value":             execTime.StirTime.MaxValue,
+			"stir_time_up_middle_value":       execTime.StirTime.UpMiddleValue,
+			"stir_time_middle_value":          execTime.StirTime.MiddleValue,
+			"stir_time_down_middle_value":     execTime.StirTime.DownMiddleValue,
+			"stir_time_min_value":             execTime.StirTime.MinValue,
+		}
 
-		res.Data = map[string]*model.ExecTimeData{
-			pastureName: {
-				AddFeedTime:  execTime.AddFeedTime,
-				SprinkleTime: execTime.SprinkleTime,
-				StirTime:     execTime.StirTime,
+		res.Data.TableList = append(res.Data.TableList, tableList)
+	}
+
+	return res, nil
+}
+
+func (s *StoreEntry) SprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (*model.SprinkleFeedTimeResponse, error) {
+	res := &model.SprinkleFeedTimeResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.SprinkleFeedTimeData{
+			Chart: &model.SprinkleFeedTimeChart{
+				Title:              make([]string, 0),
+				SprinkleNumberList: make([][]int32, 0),
 			},
+			TableList: make([]*model.SprinkleFeedTimeTable, 0),
+		},
+	}
+	pastureSprinkleDataList, err := s.PastureSprinkleFeedTime(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	tableList := make([]*model.SprinkleFeedTimeTable, 0)
+	for pastureName, data := range pastureSprinkleDataList {
+		sprinkleFeedTimeList := make(map[int32]map[int32][]int64, 0)
+		for _, v := range data {
+			tableList = append(tableList, &model.SprinkleFeedTimeTable{
+				PastureName:             pastureName,
+				BarnName:                v.FName,
+				ClassNumber:             fmt.Sprintf("%d", v.Times),
+				RealitySprinkleFeedTime: tool.TimeSub(v.InTime, v.ProcessTime),
+			})
+			realityTime := tool.TimeSub(v.InTime, v.ProcessTime)
+			realityTimeUnix, _ := time.Parse(model.LayoutTime, realityTime)
+			if sprinkleFeedTimeList[v.FBarId] == nil {
+				sprinkleFeedTimeList[v.FBarId] = make(map[int32][]int64, 0)
+			}
+			sprinkleFeedTimeList[v.FBarId][v.Times] = append(sprinkleFeedTimeList[v.FBarId][v.Times], realityTimeUnix.Unix())
 		}
+		res.Data.Chart.Title = append(res.Data.Chart.Title, pastureName)
+		res.Data.Chart.SprinkleNumberList = append(res.Data.Chart.SprinkleNumberList, sprinkleExecTimeAnalysis(sprinkleFeedTimeList))
 	}
 
+	res.Data.TableList = tableList
 	return res, nil
 }
+
+func sprinkleExecTimeAnalysis(sprinkleFeedTimeList map[int32]map[int32][]int64) []int32 {
+	res := make([]int32, 0)
+	var infoSprinkleNumber, errorSprinkleNumber int32 = 0, 0
+	zaplog.Info("sprinkleFeedTimeList", zap.Any("ok", sprinkleFeedTimeList))
+	if len(sprinkleFeedTimeList) <= 0 {
+		return res
+	} else {
+		for _, value := range sprinkleFeedTimeList {
+			for _, execTimeList := range value {
+				middleValue := tool.MedianInt64(execTimeList)
+				for _, v := range execTimeList {
+					if v >= middleValue-int64(compareTime) && v <= middleValue+int64(compareTime) {
+						infoSprinkleNumber += 1
+					} else {
+						errorSprinkleNumber += 1
+					}
+				}
+			}
+		}
+		res = append(res, infoSprinkleNumber, errorSprinkleNumber)
+	}
+
+	return res
+}

+ 1 - 0
module/backend/interface.go

@@ -151,6 +151,7 @@ type StatisticService interface {
 	SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error)
 	TopPasture(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.GetPastureTopResponse, error)
 	ExecutionTime(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.ExecTimeResponse, error)
+	SprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (*model.SprinkleFeedTimeResponse, error)
 }
 
 type WxAppletService interface {

+ 4 - 3
module/backend/statistic_service.go

@@ -20,9 +20,10 @@ import (
 type PastureClientHandler func(ctx context.Context, pastureId int64, body interface{}) error
 
 const (
-	FeedFormulaDistributeUrl = "pasture/feed_formula/distribute"
-	DashboardAccuracyUrl     = "pasture/dashboard/accuracy_data"
-	DashboardExecTimeUrl     = "pasture/dashboard/process_analysis"
+	FeedFormulaDistributeUrl     = "pasture/feed_formula/distribute"
+	DashboardAccuracyUrl         = "pasture/dashboard/accuracy_data"
+	DashboardExecTimeUrl         = "pasture/dashboard/process_analysis"
+	DashboardSprinkleFeedTimeUrl = "pasture/dashboard/sprinkle_statistics"
 )
 
 // PastureDetailById 获取指定牧场详情

+ 43 - 0
pkg/tool/tool.go

@@ -4,6 +4,7 @@ import (
 	"crypto/md5"
 	"encoding/hex"
 	"fmt"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -85,6 +86,7 @@ func TimeBetween(startTime, endTime string) []string {
 
 // Median 获取切片的中位值
 func Median(nums []float64) float64 {
+	sort.Float64s(nums)
 	n := len(nums)
 	if n%2 == 0 {
 		return (nums[n/2-1] + nums[n/2]) / float64(2)
@@ -92,3 +94,44 @@ func Median(nums []float64) float64 {
 		return nums[n/2]
 	}
 }
+
+// MedianInt64 获取切片的中位值
+func MedianInt64(nums []int64) int64 {
+	sort.Slice(nums, func(i, j int) bool {
+		return nums[i] < nums[j]
+	})
+	n := len(nums)
+	if n%2 == 0 {
+		return (nums[n/2-1] + nums[n/2]) / 2
+	} else {
+		return nums[n/2]
+	}
+}
+
+// TimeSub 计算两个时间的差值
+// eg startTime => "2022-05-01 09:07:08"
+// eg endTime => "00:06:35"
+// eg return =>  2022-05-01 09:00:33
+func TimeSub(startTime, endTime string) string {
+	str := strings.Split(endTime, ":")
+	if len(str) < 3 {
+		return ""
+	}
+	durationStr := fmt.Sprintf("%sh%sm%ss", str[0], str[1], str[2])
+
+	timeValue, err := time.Parse(time.RFC3339, startTime)
+	if err != nil {
+		fmt.Println("解析时间出错:", err)
+		return ""
+	}
+
+	// 解析持续时间字符串为 time.Duration 类型
+	durationValue, err := time.ParseDuration(durationStr)
+	if err != nil {
+		fmt.Println("解析持续时间出错:", err)
+		return ""
+	}
+
+	// 计算时间之间的差异
+	return timeValue.Add(-durationValue).Format(Layout)
+}

+ 118 - 23
proto/go/backend/operation/statistic.pb.go

@@ -2163,6 +2163,78 @@ func (x *ValueRatio) GetValueRatio() []string {
 	return nil
 }
 
+// 首页 dashboard 撒料时间统计分析
+type SprinkleFeedTimeRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	FeedFormulaId int32   `protobuf:"varint,1,opt,name=feed_formula_id,json=feedFormulaId,proto3" json:"feed_formula_id,omitempty"` // 配方id
+	StartDate     string  `protobuf:"bytes,2,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"`                // 开始时间
+	EndDate       string  `protobuf:"bytes,3,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"`                      // 结束时间
+	PastureIds    []int32 `protobuf:"varint,4,rep,packed,name=pasture_ids,json=pastureIds,proto3" json:"pasture_ids,omitempty"`     //牧场ids
+}
+
+func (x *SprinkleFeedTimeRequest) Reset() {
+	*x = SprinkleFeedTimeRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[22]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SprinkleFeedTimeRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SprinkleFeedTimeRequest) ProtoMessage() {}
+
+func (x *SprinkleFeedTimeRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[22]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SprinkleFeedTimeRequest.ProtoReflect.Descriptor instead.
+func (*SprinkleFeedTimeRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{22}
+}
+
+func (x *SprinkleFeedTimeRequest) GetFeedFormulaId() int32 {
+	if x != nil {
+		return x.FeedFormulaId
+	}
+	return 0
+}
+
+func (x *SprinkleFeedTimeRequest) GetStartDate() string {
+	if x != nil {
+		return x.StartDate
+	}
+	return ""
+}
+
+func (x *SprinkleFeedTimeRequest) GetEndDate() string {
+	if x != nil {
+		return x.EndDate
+	}
+	return ""
+}
+
+func (x *SprinkleFeedTimeRequest) GetPastureIds() []int32 {
+	if x != nil {
+		return x.PastureIds
+	}
+	return nil
+}
+
 type Table_TableList struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2175,7 +2247,7 @@ type Table_TableList struct {
 func (x *Table_TableList) Reset() {
 	*x = Table_TableList{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_backend_operation_statistic_proto_msgTypes[22]
+		mi := &file_backend_operation_statistic_proto_msgTypes[23]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2188,7 +2260,7 @@ func (x *Table_TableList) String() string {
 func (*Table_TableList) ProtoMessage() {}
 
 func (x *Table_TableList) ProtoReflect() protoreflect.Message {
-	mi := &file_backend_operation_statistic_proto_msgTypes[22]
+	mi := &file_backend_operation_statistic_proto_msgTypes[23]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2609,8 +2681,18 @@ var file_backend_operation_statistic_proto_rawDesc = []byte{
 	0x65, 0x44, 0x61, 0x79, 0x22, 0x2d, 0x0a, 0x0a, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x61, 0x74,
 	0x69, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69,
 	0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x61,
-	0x74, 0x69, 0x6f, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x74, 0x69, 0x6f, 0x22, 0x9c, 0x01, 0x0a, 0x17, 0x53, 0x70, 0x72, 0x69, 0x6e, 0x6b, 0x6c, 0x65,
+	0x46, 0x65, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x26, 0x0a, 0x0f, 0x66, 0x65, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f,
+	0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x65, 0x65, 0x64, 0x46, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74,
+	0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61,
+	0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61,
+	0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74,
+	0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x73,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x49,
+	0x64, 0x73, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -2625,7 +2707,7 @@ func file_backend_operation_statistic_proto_rawDescGZIP() []byte {
 	return file_backend_operation_statistic_proto_rawDescData
 }
 
-var file_backend_operation_statistic_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
+var file_backend_operation_statistic_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
 var file_backend_operation_statistic_proto_goTypes = []interface{}{
 	(*SearchFormulaEstimateRequest)(nil),         // 0: backend.operation.SearchFormulaEstimateRequest
 	(*SearchInventoryStatisticsRequest)(nil),     // 1: backend.operation.SearchInventoryStatisticsRequest
@@ -2649,25 +2731,26 @@ var file_backend_operation_statistic_proto_goTypes = []interface{}{
 	(*Table)(nil),                                // 19: backend.operation.Table
 	(*CommonValueRatio)(nil),                     // 20: backend.operation.CommonValueRatio
 	(*ValueRatio)(nil),                           // 21: backend.operation.ValueRatio
-	(*Table_TableList)(nil),                      // 22: backend.operation.Table.TableList
-	(*PaginationModel)(nil),                      // 23: backend.operation.PaginationModel
-	(*FormulaOptionEnum)(nil),                    // 24: backend.operation.FormulaOptionEnum
-	(CattleCategoryParent_Kind)(0),               // 25: backend.operation.CattleCategoryParent.Kind
+	(*SprinkleFeedTimeRequest)(nil),              // 22: backend.operation.SprinkleFeedTimeRequest
+	(*Table_TableList)(nil),                      // 23: backend.operation.Table.TableList
+	(*PaginationModel)(nil),                      // 24: backend.operation.PaginationModel
+	(*FormulaOptionEnum)(nil),                    // 25: backend.operation.FormulaOptionEnum
+	(CattleCategoryParent_Kind)(0),               // 26: backend.operation.CattleCategoryParent.Kind
 }
 var file_backend_operation_statistic_proto_depIdxs = []int32{
-	23, // 0: backend.operation.SearchFormulaEstimateRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 1: backend.operation.SearchInventoryStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 2: backend.operation.SearchUserMaterialsStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 3: backend.operation.SearchPriceStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 4: backend.operation.SearchFeedStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 5: backend.operation.CowsAnalysisRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 6: backend.operation.MixFeedStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 7: backend.operation.SprinkleStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 8: backend.operation.ProcessAnalysisRequest.pagination:type_name -> backend.operation.PaginationModel
-	23, // 9: backend.operation.TrainNumberRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 0: backend.operation.SearchFormulaEstimateRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 1: backend.operation.SearchInventoryStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 2: backend.operation.SearchUserMaterialsStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 3: backend.operation.SearchPriceStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 4: backend.operation.SearchFeedStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 5: backend.operation.CowsAnalysisRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 6: backend.operation.MixFeedStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 7: backend.operation.SprinkleStatisticsRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 8: backend.operation.ProcessAnalysisRequest.pagination:type_name -> backend.operation.PaginationModel
+	24, // 9: backend.operation.TrainNumberRequest.pagination:type_name -> backend.operation.PaginationModel
 	14, // 10: backend.operation.TrainNumberResponse.data:type_name -> backend.operation.TrainNumberData
-	24, // 11: backend.operation.TrainNumberData.list:type_name -> backend.operation.FormulaOptionEnum
-	25, // 12: backend.operation.SearchAnalysisAccuracyRequest.cattle_parent_category_id:type_name -> backend.operation.CattleCategoryParent.Kind
+	25, // 11: backend.operation.TrainNumberData.list:type_name -> backend.operation.FormulaOptionEnum
+	26, // 12: backend.operation.SearchAnalysisAccuracyRequest.cattle_parent_category_id:type_name -> backend.operation.CattleCategoryParent.Kind
 	17, // 13: backend.operation.SearchAnalysisAccuracyResponse.data:type_name -> backend.operation.AnalysisAccuracy
 	18, // 14: backend.operation.AnalysisAccuracy.chart:type_name -> backend.operation.Chart
 	19, // 15: backend.operation.AnalysisAccuracy.table:type_name -> backend.operation.Table
@@ -2675,7 +2758,7 @@ var file_backend_operation_statistic_proto_depIdxs = []int32{
 	20, // 17: backend.operation.Chart.mixed_fodder_correct_ratio:type_name -> backend.operation.CommonValueRatio
 	20, // 18: backend.operation.Chart.sprinkle_fodder_accurate_ratio:type_name -> backend.operation.CommonValueRatio
 	20, // 19: backend.operation.Chart.sprinkle_fodder_correct_ratio:type_name -> backend.operation.CommonValueRatio
-	22, // 20: backend.operation.Table.table_list:type_name -> backend.operation.Table.TableList
+	23, // 20: backend.operation.Table.table_list:type_name -> backend.operation.Table.TableList
 	21, // 21: backend.operation.CommonValueRatio.data_list:type_name -> backend.operation.ValueRatio
 	22, // [22:22] is the sub-list for method output_type
 	22, // [22:22] is the sub-list for method input_type
@@ -2958,6 +3041,18 @@ func file_backend_operation_statistic_proto_init() {
 			}
 		}
 		file_backend_operation_statistic_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SprinkleFeedTimeRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Table_TableList); i {
 			case 0:
 				return &v.state
@@ -2976,7 +3071,7 @@ func file_backend_operation_statistic_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_backend_operation_statistic_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   23,
+			NumMessages:   24,
 			NumExtensions: 0,
 			NumServices:   0,
 		},