瀏覽代碼

feeding: 饲喂模块更新

Yi 1 周之前
父節點
當前提交
51b621bc8e

+ 1 - 1
go.mod

@@ -3,7 +3,7 @@ module kpt-pasture
 go 1.17
 
 require (
-	gitee.com/xuyiping_admin/go_proto v0.0.0-20250704074215-792bdf94697f
+	gitee.com/xuyiping_admin/go_proto v0.0.0-20250708055755-eca7bde9521e
 	gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/eclipse/paho.mqtt.golang v1.4.3

+ 2 - 0
go.sum

@@ -1321,6 +1321,8 @@ gitee.com/xuyiping_admin/go_proto v0.0.0-20250704015606-a7700f1dd25c h1:w4zzRgZm
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250704015606-a7700f1dd25c/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250704074215-792bdf94697f h1:T8pnxQrMhpNZE06hiiK+EQinW7R8ttfbEUtTjTPUrLM=
 gitee.com/xuyiping_admin/go_proto v0.0.0-20250704074215-792bdf94697f/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250708055755-eca7bde9521e h1:6J5ou2TgMj+zX910ELsJj0XRq1uVKGyNcdSx3Uoj2NE=
+gitee.com/xuyiping_admin/go_proto v0.0.0-20250708055755-eca7bde9521e/go.mod h1:BKrFW6YLDectlQcQk3FYKBeXvjEiodAKJ5rq7O/QiPE=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0 h1:ZCOqEAnGm6+DTAhACigzWKbwMKtleb8/7OzP2xfHG7g=
 gitee.com/xuyiping_admin/pkg v0.0.0-20250613101634-36c36a2d27d0/go.mod h1:8tF25X6pE9WkFCczlNAC0K2mrjwKvhhp02I7o0HtDxY=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

+ 1 - 1
http/route/feeding.go → http/route/feeding_api.go

@@ -13,7 +13,7 @@ func FeedingAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		}
 		feedingRoute := authRouteGroup(s, "/api/v1/feeding/")
 
-		feedingRoute.GET("tmrdata", feeding.GetFeedingHomepage)
+		feedingRoute.GET("tmr/data", feeding.GetFeedingHomepage)
 		feedingRoute.GET("management", feeding.GetFeedingManagement)
 	}
 }

+ 2 - 2
model/app_pasture_list.go

@@ -21,14 +21,14 @@ type AppPastureList struct {
 	PlanScale            string                         `json:"planScale"`
 	AppId                string                         `json:"appId"`
 	Status               int32                          `json:"status"`
+	FeedPastureId        int64                          `json:"feedPastureId"`
+	FeedPastureUrl       string                         `json:"feedPastureUrl"`
 	IsShow               pasturePb.IsShow_Kind          `json:"isShow"`
 	ProductionModel      int32                          `json:"productionModel"`
 	Remarks              string                         `json:"remarks"`
 	CreatedName          string                         `json:"createdName"`
 	CreatedAt            int64                          `json:"createdAt"`
 	UpdatedAt            string                         `json:"updatedAt"`
-	PastureId            int64                          `json:"pastureId"`
-	PastureUrl           string                         `json:"pastureUrl"`
 }
 
 func (a *AppPastureList) TableName() string {

+ 0 - 39
model/feeding.go

@@ -1,39 +0,0 @@
-package model
-
-type FeedingHomepageResponse struct {
-	Code int32                  `json:"code"`
-	Msg  string                 `json:"msg"`
-	Data []*FeedingHomepageData `json:"data"`
-}
-
-type FeedingHomepageData struct {
-	Cost       string `json:"cost"`       // "0.00"
-	DoneTimes  string `json:"doneTimes"`  // "23"
-	DoneWeight string `json:"doneWeight"` // "0.00"
-	MonRateHL  string `json:"monRateHL"`  // "100.03%"
-	MonRateSL  string `json:"monRateSL"`  // "98.66%"
-	MonRateSLR string `json:"monRateSLR"` // "69.59%"
-	Montime    string `json:"montime"`    // "5.41"
-	PlanTimes  string `json:"planTimes"`  // "41"
-	PlanWeight string `json:"planWeight"` // "0.00"
-	Temtime    string `json:"temtime"`    // "7.04"
-	TotalCost  string `json:"totalCost"`  // "0.00"
-	YesRateHL  string `json:"yesRateHL"`  // "99.92%"
-	YesRateSL  string `json:"yesRateSL"`  // "99.92%"
-	YesRateSLR string `json:"yesRateSLR"` // "72.54%"
-}
-
-type FeedingManagementResponse struct {
-	Code int32                    `json:"code"`
-	Msg  string                   `json:"msg"`
-	Data []*FeedingManagementData `json:"data"`
-}
-
-type FeedingManagementData struct {
-	ActualWeightMinus string `json:"actualweightminus"` // "15456.23"
-	CCount            string `json:"ccount"`            // "147"
-	Cost              string `json:"cost"`              // "0.00"
-	DryWeight         string `json:"dryweight"`         // "0.00"
-	TotalCost         string `json:"totalcost"`         // "0.00"
-	TypeA             string `json:"typea"`             // "11"
-}

+ 35 - 127
module/backend/feeding.go

@@ -1,167 +1,75 @@
 package backend
 
 import (
-	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
-	"kpt-pasture/model"
-	"net/http"
-	"time"
+	"kpt-pasture/service/httpclient"
 
 	feedingPb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
-	"gitee.com/xuyiping_admin/pkg/logger/zaplog"
 	"gitee.com/xuyiping_admin/pkg/xerr"
-	"go.uber.org/zap"
 )
 
-type FeedingHttp struct {
-	authClient *http.Client
+var FeedingHeaders = []*httpclient.Header{
+	{
+		Key:   "Content-Type",
+		Value: "application/json",
+	}, {
+		Key:   "Accept",
+		Value: "application/json",
+	},
 }
 
-func NewFeedingService() *FeedingHttp {
-	return &FeedingHttp{
-		authClient: &http.Client{
-			Timeout: time.Duration(60) * time.Second,
-		},
-	}
-}
-
-func (c *FeedingHttp) doRequest(req *http.Request, response interface{}) ([]byte, error) {
-	resp, err := http.DefaultClient.Do(req)
-	if err != nil {
-		zaplog.Error("ClientService", zap.Any("authClient.Do", err))
-		return nil, xerr.WithStack(err)
-	}
-	b, err := ioutil.ReadAll(resp.Body)
+func (s *StoreEntry) GetFeedingHomepage(ctx context.Context, req *feedingPb.FeedingHomepageRequest) (*feedingPb.FeedingHomepageResponse, error) {
+	userModel, err := s.GetUserModel(ctx)
 	if err != nil {
-		zaplog.Error("ClientService", zap.Any("ioutil.ReadAll", err))
-		return nil, xerr.WithStack(err)
-	}
-	if resp.StatusCode != http.StatusOK {
-		if len(b) > 0 {
-			return nil, xerr.Customf("err:%v,body:%s", err, string(b))
-		} else {
-			return nil, xerr.Customf("err:%v", err)
-		}
-	}
-	fmt.Println(string(b))
-	if err = json.Unmarshal(b, response); err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	return b, nil
-}
-
-func (c *FeedingHttp) DoGet(url string, response interface{}) ([]byte, error) {
-	req, err := http.NewRequest(http.MethodGet, url, nil)
-	if err != nil {
-		zaplog.Error("ClientService", zap.Any("DoGet", err))
-		return nil, err
+	pasture := userModel.AppPasture
+	if pasture.FeedPastureId == 0 {
+		return nil, xerr.Customf("饲喂数据未配置")
 	}
-	req.Header.Add("Accept", "application/json")
-	req.Header.Add("Content-Type", "application/json")
-	return c.doRequest(req, response)
-}
 
-func (c *FeedingHttp) DoPost(url string, body interface{}, response *interface{}) ([]byte, error) {
-	b, err := json.Marshal(body)
-	if err != nil {
-		zaplog.Error("ClientService", zap.Any("DoPost-Marshal", err))
-		return nil, xerr.WithStack(err)
+	url := fmt.Sprintf("%d/feeding/tmrdata", pasture.FeedPastureId)
+	res := &feedingPb.FeedingHomepageResponse{
+		Code: 0,
+		Msg:  "",
+		Data: make([]*feedingPb.FeedingHomepageData, 0),
 	}
-	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
+	result, err := s.HttpClient.DoGet(url, FeedingHeaders)
 	if err != nil {
-		zaplog.Error("ClientService", zap.Any("NewRequest", err))
-		return nil, xerr.WithStack(err)
-	}
-	req.Header.Add("Accept", "application/json")
-	req.Header.Add("Content-Type", "application/json")
-	return c.doRequest(req, response)
-}
-
-func (s *StoreEntry) GetFeedingHomepage(ctx context.Context, req *feedingPb.FeedingHomepageRequest) (*feedingPb.FeedingHomepageResponse, error) {
-	var pasture model.AppPastureList
-	if err := s.DB.Debug().Table(new(model.AppPastureList).TableName()).Where("farm_id = ?", req.FarmId).First(&pasture).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	if pasture.PastureId == 0 {
-		return nil, xerr.Customf("牧场不存在")
-	}
 
-	url := fmt.Sprintf("%s/feeding/tmrdata", pasture.PastureUrl)
-	fmt.Println(url)
-	client := NewFeedingService()
-
-	response := &model.FeedingHomepageResponse{Data: []*model.FeedingHomepageData{}}
-	_, err := client.DoGet(url, response)
-	if err != nil {
+	if err = json.Unmarshal(result, res); err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	fmt.Println(response)
-	res := &feedingPb.FeedingHomepageResponse{
-		Code: response.Code,
-		Msg:  response.Msg,
-		Data: &feedingPb.FeedingHomepageData{},
-	}
-
-	for _, v := range response.Data {
-		res.Data = &feedingPb.FeedingHomepageData{
-			Cost:       v.Cost,
-			DoneTimes:  v.DoneTimes,
-			DoneWeight: v.DoneWeight,
-			MonRateHL:  v.MonRateHL,
-			MonRateSL:  v.MonRateSL,
-			MonRateSLR: v.MonRateSLR,
-			Montime:    v.Montime,
-			PlanTimes:  v.PlanTimes,
-			PlanWeight: v.PlanWeight,
-			Temtime:    v.Temtime,
-			TotalCost:  v.TotalCost,
-			YesRateHL:  v.YesRateHL,
-			YesRateSL:  v.YesRateSL,
-			YesRateSLR: v.YesRateSLR,
-		}
-	}
-
 	return res, nil
 }
 
 func (s *StoreEntry) GetFeedingManagement(ctx context.Context, req *feedingPb.FeedingManagementRequest) (*feedingPb.FeedingManagementResponse, error) {
-	var pasture model.AppPastureList
-	if err := s.DB.Debug().Table(new(model.AppPastureList).TableName()).Where("farm_id = ?", req.FarmId).First(&pasture).Error; err != nil {
+	userModel, err := s.GetUserModel(ctx)
+	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	if pasture.PastureId == 0 {
-		return nil, xerr.Customf("牧场不存在")
+	pasture := userModel.AppPasture
+	if pasture.FeedPastureId == 0 {
+		return nil, xerr.Customf("饲喂数据未配置")
 	}
 
-	url := fmt.Sprintf("%s/feeding/management?typea=%s&startdate=%s&enddate=%s", pasture.PastureUrl, req.Typea, req.Startdate, req.Enddate)
-	fmt.Println(url)
-	client := NewFeedingService()
-
-	response := &model.FeedingManagementResponse{Data: []*model.FeedingManagementData{}}
-	_, err := client.DoGet(url, response)
-	if err != nil {
-		return nil, xerr.WithStack(err)
-	}
-	fmt.Println(response)
+	url := fmt.Sprintf("%d/feeding/management?typea=%s&startdate=%s&enddate=%s", pasture.FeedPastureId, req.Typea, req.Startdate, req.Enddate)
 	res := &feedingPb.FeedingManagementResponse{
-		Code: response.Code,
-		Msg:  response.Msg,
+		Code: 0,
+		Msg:  "",
 		Data: []*feedingPb.FeedingManagementData{},
 	}
-
-	for _, v := range response.Data {
-		res.Data = append(res.Data, &feedingPb.FeedingManagementData{
-			Actualweightminus: v.ActualWeightMinus,
-			Ccount:            v.CCount,
-			Cost:              v.Cost,
-			Dryweight:         v.DryWeight,
-			Totalcost:         v.TotalCost,
-			Typea:             v.TypeA,
-		})
+	result, err := s.HttpClient.DoGet(url, FeedingHeaders)
+	if err != nil {
+		return nil, xerr.WithStack(err)
 	}
 
+	if err = json.Unmarshal(result, res); err != nil {
+		return nil, xerr.WithStack(err)
+	}
 	return res, nil
 }

+ 6 - 6
module/backend/interface.go

@@ -60,8 +60,8 @@ type KptService interface {
 	MilkHallService      // 奶厅数据相关
 	UploadService        // 上传文件相关
 	WarningService       // 预警相关
-	TestService          // 测试相关
 	FeedingService       // 饲喂相关
+	TestService          // 测试相关
 }
 
 //go:generate mockgen -destination mock/SystemService.go -package kptservicemock kpt-pasture/module/backend SystemService
@@ -367,6 +367,11 @@ type UploadService interface {
 	ImportExcel2(ctx context.Context, data [][]string, excelHeader []string) error
 }
 
+type FeedingService interface {
+	GetFeedingHomepage(ctx context.Context, req *pasturePb.FeedingHomepageRequest) (*pasturePb.FeedingHomepageResponse, error)
+	GetFeedingManagement(ctx context.Context, req *pasturePb.FeedingManagementRequest) (*pasturePb.FeedingManagementResponse, error)
+}
+
 type TestService interface {
 	CowNeckRingNumberBound(ctx context.Context, pagination *pasturePb.PaginationModel) error
 	CowNeckRingNumberBound2(ctx context.Context, pagination *pasturePb.PaginationModel) error
@@ -378,8 +383,3 @@ type TestService interface {
 	CalvingAge(ctx context.Context) error
 	SystemMenuInit(ctx context.Context) error
 }
-
-type FeedingService interface {
-	GetFeedingHomepage(ctx context.Context, req *pasturePb.FeedingHomepageRequest) (*pasturePb.FeedingHomepageResponse, error)
-	GetFeedingManagement(ctx context.Context, req *pasturePb.FeedingManagementRequest) (*pasturePb.FeedingManagementResponse, error)
-}

+ 8 - 7
module/crontab/neck_ring_calculate.go

@@ -20,7 +20,7 @@ func (e *Entry) NeckRingCalculate() error {
 		return nil
 	}
 	for _, pasture := range pastureList {
-		if err := e.EntryUpdateActiveHabit(pasture.Id); err != nil {
+		if err := e.EntryUpdateActiveHabit(pasture); err != nil {
 			zaplog.Error("NeckRingCalculate", zap.Any("err", err), zap.Any("pasture", pasture))
 		}
 		zaplog.Info(fmt.Sprintf("NeckRingCalculate Success %d", pasture.Id))
@@ -28,7 +28,8 @@ func (e *Entry) NeckRingCalculate() error {
 	return nil
 }
 
-func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
+func (e *Entry) EntryUpdateActiveHabit(appPasture *model.AppPastureList) (err error) {
+	pastureId := appPasture.Id
 	// 获取这段执行数据内最大日期和最小日期
 	xToday, err := e.XToday(pastureId)
 	if err != nil {
@@ -57,7 +58,7 @@ func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
 	e.SecondUpdateChangeFilter(pastureId, processIds, xToday)
 
 	// 活动量校正系数和健康评分
-	e.FilterCorrectAndScoreUpdate(pastureId, processIds, xToday)
+	e.FilterCorrectAndScoreUpdate(appPasture, processIds, xToday)
 
 	// 更新 ChangeFilter
 	e.UpdateChangeFilter(pastureId, processIds)
@@ -279,7 +280,7 @@ func (e *Entry) SecondUpdateChangeFilter(pastureId int64, processIds []int64, xT
 }
 
 // FilterCorrectAndScoreUpdate 计算活动量变化趋势校正值(活跃度校正)和健康评分
-func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64, xToday *XToday) {
+func (e *Entry) FilterCorrectAndScoreUpdate(appPasture *model.AppPastureList, processIds []int64, xToday *XToday) {
 	beginDayDate := time.Now().Local()
 	before7DayDate := beginDayDate.AddDate(0, 0, -7).Format(model.LayoutDate2)
 	before1DayDate := beginDayDate.AddDate(0, 0, -1).Format(model.LayoutDate2)
@@ -287,14 +288,14 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64,
 	neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
 	if err := e.DB.Model(new(model.NeckActiveHabit)).
 		Where("id IN (?)", processIds).
-		Where("pasture_id = ?", pastureId).
+		Where("pasture_id = ?", appPasture.Id).
 		Find(&neckActiveHabitList).Error; err != nil {
 		zaplog.Error("ActivityVolumeChanges-1", zap.Any("error", err), zap.Any("xToday", xToday))
 		return
 	}
 
 	for _, v := range neckActiveHabitList {
-		cowScore := calculateScore(v)
+		cowScore := CalculateScore(appPasture, v)
 		if err := e.DB.Model(new(model.NeckActiveHabit)).
 			Where("id = ?", v.Id).
 			Update("score", cowScore).Error; err != nil {
@@ -306,7 +307,7 @@ func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, processIds []int64,
 			Select("neck_ring_number", "AVG(IF(change_filter>=60, 60, change_filter)) as avg_filter",
 				"ROUND(STD(IF(change_filter>=60, 60, change_filter))) as std_filter", "COUNT(1) as nb").
 			Where("heat_date BETWEEN ? AND ?", before7DayDate, before1DayDate).
-			Where("pasture_id = ?", pastureId).
+			Where("pasture_id = ?", appPasture.Id).
 			Where(e.DB.Where("high > ?", 12).Or("rumina >= ?", xToday.Rumina)).
 			Where("active_time <= ?", beginDayDate.Add(-12*time.Hour).Format(model.LayoutTime)).
 			Where("change_filter > ?", MinChangeFilter).

+ 47 - 9
module/crontab/neck_ring_merge.go

@@ -219,8 +219,8 @@ func computeIfPositiveElse(newValue, prevFilterValue float64, weightPrev, weight
 	return math.Ceil((prevFilterValue * weightPrev) + (weightNew * newValue))
 }
 
-// 计算 score 的逻辑
-func calculateScore(habit *model.NeckActiveHabit) int {
+// CalculateScore 计算 score 的逻辑
+func CalculateScore(appPasture *model.AppPastureList, habit *model.NeckActiveHabit) int {
 	// 第一部分逻辑
 	var part1 float64
 	switch {
@@ -231,9 +231,17 @@ func calculateScore(habit *model.NeckActiveHabit) int {
 	case habit.CalvingAge >= 2 && habit.CalvingAge <= 13:
 		part1 = math.Min((float64(habit.SumRumina+habit.SumIntake)-(100+math.Min(7, float64(habit.CalvingAge))*60))/10*2, 0)
 	case habit.ChangeFilter > -99:
-		part1 = math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinHigh), 0)))*0.2 +
-			math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0)))*0.2 +
-			getRuminaSumIntakeSumScore(float64(habit.SumRumina+habit.SumIntake)) + getAdditionalScore(habit)
+		if appPasture.Category == pasturePb.PastureCategory_Beef {
+			part1 = math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter)-math.Min(float64(habit.ChangeAdjust), 0), 0), getValueOrDefault(float64(habit.SumMinHigh), 0)))*0.2 +
+				getBeefSportsRuminaScore(habit) +
+				getBeefRuminaSumIntakeSumScore(habit) +
+				getBeefAdditionalScore(habit)
+		} else {
+			part1 = math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinHigh), 0)))*0.2 +
+				getCowSportsRuminaScore(habit) +
+				getCowRuminaSumIntakeSumScore(habit) +
+				getCowAdditionalScore(habit)
+		}
 	default:
 		part1 = -299
 	}
@@ -260,8 +268,20 @@ func getValueOrDefault(value, defaultValue float64) float64 {
 	return defaultValue
 }
 
-// 计算累计反刍得分
-func getRuminaSumIntakeSumScore(sum float64) float64 {
+// 奶牛运动得反刍分
+// LEAST(0, IF(h.chew_filter>-99, h.chew_filter, 0), IF(h.sum_min_chew>-99, h.sum_min_chew, 0))*0.2
+func getCowSportsRuminaScore(habit *model.NeckActiveHabit) float64 {
+	return math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0))) * 0.2
+}
+
+// 肉牛运动得反刍分
+func getBeefSportsRuminaScore(habit *model.NeckActiveHabit) float64 {
+	return math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0))) * 0.2
+}
+
+// 奶牛累计反刍得分
+func getCowRuminaSumIntakeSumScore(habit *model.NeckActiveHabit) float64 {
+	sum := float64(habit.SumRumina + habit.SumIntake)
 	switch {
 	case sum < 80:
 		return -30
@@ -274,8 +294,14 @@ func getRuminaSumIntakeSumScore(sum float64) float64 {
 	}
 }
 
-// 计算额外得分
-func getAdditionalScore(habit *model.NeckActiveHabit) float64 {
+// 肉牛累计反刍得分
+func getBeefRuminaSumIntakeSumScore(habit *model.NeckActiveHabit) float64 {
+	sum := float64(habit.SumRumina + habit.SumIntake)
+	return math.Min(0, sum-380/10)
+}
+
+// 奶牛当前变化趋势是否是峰值
+func getCowAdditionalScore(habit *model.NeckActiveHabit) float64 {
 	var score float64
 	if (habit.SumRumina+habit.SumIntake < 280 || habit.SumMinHigh+habit.SumMinChew < -50) && habit.SumMaxHigh > 50 {
 		score += 10
@@ -285,3 +311,15 @@ func getAdditionalScore(habit *model.NeckActiveHabit) float64 {
 	}
 	return score
 }
+
+// 肉牛当前变化趋势是否是峰值
+// IF((h.change_filter -LEAST(h.change_adjust,0))<-30 AND (h.change_filter -LEAST(h.change_adjust,0))<=h.sum_min_high AND h.chew_filter<-30 AND h.chew_filter<=h.sum_min_chew, -5, 0)
+func getBeefAdditionalScore(habit *model.NeckActiveHabit) float64 {
+	var score float64
+	if (float64(habit.ChangeFilter)-math.Min(float64(habit.ChangeAdjust), 0)) < -30 &&
+		(float64(habit.ChangeFilter)-math.Min(float64(habit.ChangeAdjust), 0)) <= float64(habit.SumMinHigh) &&
+		habit.ChewFilter < -30 && habit.ChewFilter <= habit.SumMinChew {
+		score -= 5
+	}
+	return score
+}

+ 7 - 0
module/crontab/neck_ring_merge_test.go

@@ -0,0 +1,7 @@
+package crontab
+
+import "testing"
+
+func TestEntry_CalculateScore(t *testing.T) {
+
+}

+ 1 - 1
service/httpclient/http.go

@@ -21,7 +21,7 @@ type Service struct {
 func NewClientService() *Service {
 	return &Service{
 		authClient: &http.Client{
-			Timeout: time.Duration(5) * time.Second,
+			Timeout: time.Duration(60) * time.Second,
 		},
 	}
 }