neck_ring_merge.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. package crontab
  2. import (
  3. "fmt"
  4. "kpt-pasture/model"
  5. "kpt-pasture/util"
  6. "math"
  7. "sort"
  8. "strings"
  9. "time"
  10. pasturePb "gitee.com/xuyiping_admin/go_proto/proto/go/backend/cow"
  11. "gitee.com/xuyiping_admin/pkg/logger/zaplog"
  12. "gitee.com/xuyiping_admin/pkg/xerr"
  13. "go.uber.org/zap"
  14. )
  15. const (
  16. MinChangeFilter = -99
  17. MinRuminaFilter = -99
  18. MinChewFilter = -99
  19. MinChangeHigh = -99
  20. DefaultNb = 30
  21. DefaultScore = 100
  22. )
  23. var (
  24. defaultLimit = int32(1000)
  25. calculateIsRunning bool
  26. )
  27. // NeckRingOriginalMerge 把脖环数据合并成2个小时的
  28. func (e *Entry) NeckRingOriginalMerge() (err error) {
  29. if ok := e.IsExistCrontabLog(NeckRingOriginal); !ok {
  30. newTime := time.Now()
  31. e.CreateCrontabLog(NeckRingOriginal)
  32. // 原始数据删除15天前的
  33. e.DB.Model(new(model.NeckRingOriginal)).
  34. Where("created_at < ?", newTime.AddDate(0, 0, -7).Unix()).
  35. Delete(new(model.NeckRingOriginal))
  36. // 活动数据删除6个月前的数据
  37. e.DB.Model(new(model.NeckActiveHabit)).
  38. Where("created_at < ?", newTime.AddDate(0, -2, 0).Unix()).
  39. Delete(new(model.NeckActiveHabit))
  40. }
  41. pastureList := e.FindPastureList()
  42. if pastureList == nil || len(pastureList) == 0 {
  43. return nil
  44. }
  45. for _, pasture := range pastureList {
  46. if err = e.OriginalMergeData(pasture.Id); err != nil {
  47. zaplog.Error("NeckRingOriginalMerge", zap.Any("OriginalMergeData", err), zap.Any("pasture", pasture))
  48. }
  49. }
  50. return nil
  51. }
  52. func (e *Entry) OriginalMergeData(pastureId int64) error {
  53. limit := e.Cfg.NeckRingLimit
  54. if limit <= 0 {
  55. limit = defaultLimit
  56. }
  57. neckRingList := make([]*model.NeckRingOriginal, 0)
  58. if err := e.DB.Model(new(model.NeckRingOriginal)).
  59. Where("is_show = ?", pasturePb.IsShow_No).
  60. Where("pasture_id = ?", pastureId).
  61. Limit(int(limit)).Find(&neckRingList).Error; err != nil {
  62. return xerr.WithStack(err)
  63. }
  64. if len(neckRingList) <= 0 {
  65. return nil
  66. }
  67. // 去重
  68. neckRingList = RemoveDuplicates(neckRingList)
  69. // 计算合并
  70. neckActiveHabitList := Recalculate(neckRingList)
  71. if len(neckActiveHabitList) <= 0 {
  72. return nil
  73. }
  74. for _, habit := range neckActiveHabitList {
  75. //更新脖环牛只相关信息 新数据直接插入
  76. historyNeckActiveHabit, ct := e.IsExistNeckActiveHabit(pastureId, habit.NeckRingNumber, habit.HeatDate, habit.Frameid)
  77. if ct <= 0 {
  78. if err := e.DB.Create(habit).Error; err != nil {
  79. zaplog.Info("NeckRingOriginalMergeData-1",
  80. zap.Any("err", err),
  81. zap.Any("neckActiveHabit", habit),
  82. )
  83. }
  84. } else {
  85. // 重新计算
  86. newNeckActiveHabit := e.againRecalculate(historyNeckActiveHabit)
  87. if newNeckActiveHabit == nil {
  88. continue
  89. }
  90. if err := e.DB.Model(new(model.NeckActiveHabit)).
  91. Select("rumina", "intake", "inactive", "gasp", "other", "high", "active", "is_show", "record_count").
  92. Where("id = ?", historyNeckActiveHabit.Id).
  93. Updates(newNeckActiveHabit).Error; err != nil {
  94. zaplog.Error("NeckRingOriginalMergeData-2",
  95. zap.Any("err", err),
  96. zap.Any("ct", ct),
  97. zap.Any("historyNeckActiveHabit", historyNeckActiveHabit),
  98. zap.Any("newNeckActiveHabit", newNeckActiveHabit),
  99. )
  100. }
  101. }
  102. if err := e.UpdateNeckRingOriginalIsShow(habit); err != nil {
  103. zaplog.Error("NeckRingOriginalMergeData-4",
  104. zap.Any("err", err),
  105. zap.Any("neckActiveHabit", habit),
  106. )
  107. }
  108. }
  109. return nil
  110. }
  111. func (e *Entry) NeckRingCalculate() error {
  112. pastureList := e.FindPastureList()
  113. if pastureList == nil || len(pastureList) == 0 {
  114. return nil
  115. }
  116. if calculateIsRunning {
  117. return nil
  118. }
  119. defer func() {
  120. calculateIsRunning = false
  121. }()
  122. calculateIsRunning = true
  123. for _, pasture := range pastureList {
  124. if err := e.EntryUpdateActiveHabit(pasture.Id); err != nil {
  125. zaplog.Error("NeckRingCalculate", zap.Any("err", err), zap.Any("pasture", pasture))
  126. }
  127. zaplog.Info(fmt.Sprintf("NeckRingCalculate Success %d", pasture.Id))
  128. }
  129. return nil
  130. }
  131. func (e *Entry) EntryUpdateActiveHabit(pastureId int64) (err error) {
  132. // 获取这段执行数据内最大日期和最小日期
  133. xToday := &XToday{}
  134. systemConfigureList, err := e.GetSystemNeckRingConfigure(pastureId)
  135. if err != nil {
  136. return xerr.WithStack(err)
  137. }
  138. for _, v := range systemConfigureList {
  139. switch v.Name {
  140. case model.MaxHabit:
  141. xToday.LastMaxHabitId = v.Value
  142. case model.High:
  143. xToday.High = int32(v.Value)
  144. case model.Rumina:
  145. xToday.Rumina = int32(v.Value)
  146. case model.XRuminaDisc:
  147. xToday.XRuminaDisc = int32(v.Value)
  148. case model.XChangeDiscount:
  149. xToday.XChangeDiscount = int32(v.Value)
  150. case model.WeeklyActive:
  151. xToday.WeeklyActive = int32(v.Value)
  152. }
  153. }
  154. currMaxHabit := &model.NeckActiveHabit{}
  155. if err = e.DB.Model(new(model.NeckActiveHabit)).
  156. Where("id > ?", xToday.LastMaxHabitId).
  157. Where("pasture_id = ?", pastureId).
  158. Where("is_show = ?", pasturePb.IsShow_No).
  159. Order("id desc").
  160. First(currMaxHabit).Error; err != nil {
  161. return xerr.WithStack(err)
  162. }
  163. if currMaxHabit.Id <= 0 || currMaxHabit.Id <= xToday.LastMaxHabitId {
  164. return nil
  165. }
  166. xToday.CurrMaxHabitId = currMaxHabit.Id
  167. var processIds []int64
  168. // 更新活动滤波
  169. processIds, err = e.FirstFilterUpdate(pastureId, xToday)
  170. if err != nil {
  171. zaplog.Error("NeckRingCalculate", zap.Any("FirstFilterUpdate", err), zap.Any("xToday", xToday))
  172. }
  173. zaplog.Info("NeckRingCalculate", zap.Any("xToday", xToday), zap.Any("processIds", processIds))
  174. if len(processIds) <= 0 {
  175. return nil
  176. }
  177. if err = e.WeeklyUpdateActiveHabit(pastureId, processIds, xToday); err != nil {
  178. zaplog.Error("NeckRingCalculate", zap.Any("WeeklyUpdateActiveHabit", err), zap.Any("xToday", xToday))
  179. }
  180. if err = e.Before3DaysNeckActiveHabit(pastureId, processIds, xToday); err != nil {
  181. zaplog.Error("NeckRingCalculate", zap.Any("Before3DaysNeckActiveHabit", err), zap.Any("xToday", xToday))
  182. }
  183. // 二次更新滤波
  184. if err = e.SecondUpdateChangeFilter(pastureId, xToday); err != nil {
  185. zaplog.Error("NeckRingCalculate", zap.Any("SecondUpdateChangeFilter", err), zap.Any("xToday", xToday))
  186. }
  187. // 活动量校正系数和健康评分
  188. if err = e.FilterCorrectAndScoreUpdate(pastureId, xToday); err != nil {
  189. zaplog.Error("NeckRingCalculate", zap.Any("ActivityVolumeChanges", err), zap.Any("xToday", xToday))
  190. }
  191. if err = e.DB.Model(new(model.NeckActiveHabit)).
  192. Where("id IN (?)", processIds).
  193. Where("pasture_id = ?", pastureId).
  194. Where("is_show = ?", pasturePb.IsShow_No).
  195. Where("change_filter = ?", model.InitChangeFilter).
  196. Updates(map[string]interface{}{
  197. "change_filter": model.DefaultChangeFilter,
  198. "rumina_filter": model.DefaultRuminaFilter,
  199. "chew_filter": model.DefaultChewFilter,
  200. }).Error; err != nil {
  201. zaplog.Error("EntryUpdateActiveHabit", zap.Any("change_filter", err), zap.Any("xToday", xToday))
  202. }
  203. if err = e.DB.Model(new(model.NeckActiveHabit)).
  204. Where("id IN (?)", processIds).
  205. Where("pasture_id = ?", pastureId).
  206. Where("change_filter < ?", 0).
  207. Where("filter_correct < ?", model.DefaultFilterCorrect).
  208. Updates(map[string]interface{}{
  209. "filter_correct": model.DefaultFilterCorrect,
  210. }).Error; err != nil {
  211. zaplog.Error("EntryUpdateActiveHabit", zap.Any("filter_correct", err), zap.Any("xToday", xToday))
  212. }
  213. // 插入群体校正表
  214. if err = e.UpdateChangeAdJust(pastureId, xToday); err != nil {
  215. zaplog.Error("EntryUpdateActiveHabit", zap.Any("UpdateChangeAdJust", err), zap.Any("xToday", xToday))
  216. }
  217. // 更新所有的显示状态为否的记录为是
  218. if err = e.DB.Model(new(model.NeckActiveHabit)).
  219. Where("id IN (?)", processIds).
  220. Where("pasture_id = ?", pastureId).
  221. Update("is_show", pasturePb.IsShow_Ok).Error; err != nil {
  222. zaplog.Error("UpdateChangeAdJust-2", zap.Any("error", err), zap.Any("xToday", xToday))
  223. }
  224. // 更新日志
  225. if err = e.DB.Model(new(model.NeckRingConfigure)).
  226. Where("name = ?", model.MaxHabit).
  227. Where("pasture_id = ?", pastureId).
  228. Update("value", processIds[len(processIds)-1]).Error; err != nil {
  229. zaplog.Error("NeckRingCalculate", zap.Any("MaxHabit", err), zap.Any("xToday", xToday))
  230. }
  231. // 健康预警
  232. if err = e.HealthWarning(pastureId, processIds); err != nil {
  233. zaplog.Error("EntryUpdateActiveHabit", zap.Any("HealthWarning", err))
  234. }
  235. return nil
  236. }
  237. // FirstFilterUpdate 首次更新活动滤波
  238. func (e *Entry) FirstFilterUpdate(pastureId int64, xToDay *XToday) (processIds []int64, err error) {
  239. newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  240. if err = e.DB.Model(new(model.NeckActiveHabit)).
  241. Where("id > ?", xToDay.LastMaxHabitId).
  242. Where("pasture_id = ?", pastureId).
  243. Where("is_show = ?", pasturePb.IsShow_No).
  244. //Where("record_count = ?", model.DefaultRecordCount).
  245. Where(e.DB.Where("high >= ?", xToDay.High).Or("rumina >= ?", xToDay.Rumina)).
  246. Order("heat_date,neck_ring_number,frameid").
  247. Limit(int(defaultLimit)).
  248. Find(&newNeckActiveHabitList).Error; err != nil {
  249. return nil, xerr.WithStack(err)
  250. }
  251. // 活动量滤波
  252. for _, v := range newNeckActiveHabitList {
  253. // 过滤牛只未绑定的脖环的数据
  254. cowInfo := e.GetCowInfoByNeckRingNumber(v.PastureId, v.NeckRingNumber)
  255. if cowInfo == nil || cowInfo.Id <= 0 {
  256. if err = e.DB.Model(new(model.NeckActiveHabit)).
  257. Where("id = ?", v.Id).
  258. Where("pasture_id = ?", v.PastureId).
  259. Update("is_show", pasturePb.IsShow_Ok).Error; err != nil {
  260. zaplog.Error("EntryUpdateActiveHabit", zap.Any("error", err))
  261. }
  262. continue
  263. }
  264. frameId := v.Frameid
  265. heatDate := v.HeatDate
  266. if v.Frameid == 0 {
  267. frameId = 11
  268. heatDateParse, _ := time.Parse(model.LayoutDate2, heatDate)
  269. heatDate = heatDateParse.AddDate(0, 0, -1).Format(model.LayoutDate2)
  270. } else {
  271. frameId -= 1
  272. }
  273. firstFilterData := e.FindFirstFilter(pastureId, v.NeckRingNumber, heatDate, frameId)
  274. if v.FilterHigh > 0 {
  275. firstFilterData.FilterHigh = v.FilterHigh
  276. } else {
  277. if v.NeckRingNumber == firstFilterData.NeckRingNumber {
  278. firstFilterData.FilterHigh = int32(computeIfPositiveElse(float64(v.High), float64(firstFilterData.FilterHigh), 0.23, 0.77))
  279. } else {
  280. firstFilterData.FilterHigh = v.High
  281. }
  282. }
  283. if v.FilterRumina > 0 {
  284. firstFilterData.FilterRumina = v.FilterRumina
  285. } else {
  286. if v.NeckRingNumber == firstFilterData.NeckRingNumber {
  287. firstFilterData.FilterRumina = int32(computeIfPositiveElse(float64(v.Rumina), float64(firstFilterData.FilterRumina), 0.33, 0.67))
  288. } else {
  289. firstFilterData.FilterRumina = v.Rumina
  290. }
  291. }
  292. if v.FilterChew > 0 {
  293. firstFilterData.FilterChew = v.FilterChew
  294. } else {
  295. if v.NeckRingNumber == firstFilterData.NeckRingNumber {
  296. firstFilterData.FilterChew = int32(computeIfPositiveElse(float64(v.Rumina+v.Intake), float64(firstFilterData.FilterChew), 0.33, 0.67))
  297. } else {
  298. firstFilterData.FilterChew = v.Rumina + v.Intake
  299. }
  300. }
  301. processIds = append(processIds, v.Id)
  302. // 更新过滤值 // todo 记得更新胎次为牛只胎次,现在为了测试特意改成0
  303. if err = e.DB.Model(new(model.NeckActiveHabit)).
  304. Select("filter_high", "filter_rumina", "filter_chew", "cow_id", "lact", "calving_age", "ear_number").
  305. Where("id = ?", v.Id).
  306. Updates(map[string]interface{}{
  307. "filter_high": firstFilterData.FilterHigh,
  308. "filter_rumina": firstFilterData.FilterRumina,
  309. "filter_chew": firstFilterData.FilterChew,
  310. "cow_id": cowInfo.Id,
  311. "lact": 0,
  312. "calving_age": cowInfo.CalvingAge,
  313. "ear_number": cowInfo.EarNumber,
  314. }).Error; err != nil {
  315. zaplog.Error("FirstFilterUpdate",
  316. zap.Any("error", err),
  317. zap.Any("firstFilterData", firstFilterData),
  318. zap.Any("NeckActiveHabit", v),
  319. zap.Any("cowInfo", cowInfo),
  320. zap.Any("xToday", xToDay),
  321. )
  322. }
  323. }
  324. return processIds, nil
  325. }
  326. // WeeklyUpdateActiveHabit 时间点周平均值计算
  327. func (e *Entry) WeeklyUpdateActiveHabit(pastureId int64, processIds []int64, xToDay *XToday) (err error) {
  328. newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  329. if err = e.DB.Model(new(model.NeckActiveHabit)).
  330. Where("id IN (?)", processIds).
  331. Order("heat_date,neck_ring_number,frameid").
  332. Find(&newNeckActiveHabitList).Error; err != nil {
  333. return xerr.WithStack(err)
  334. }
  335. for _, v := range newNeckActiveHabitList {
  336. // 前七天的
  337. weekHabitData := e.FindWeekHabitData(pastureId, v.NeckRingNumber, v.HeatDate, v.Frameid, xToDay)
  338. // 更新过滤值
  339. if err = e.DB.Model(new(model.NeckActiveHabit)).
  340. Select("week_high_habit", "week_rumina_habit", "week_chew_habit", "week_intake_habit", "week_inactive_habit").
  341. Where("id = ?", v.Id).
  342. Updates(map[string]interface{}{
  343. "week_high_habit": weekHabitData.WeekHighHabit,
  344. "week_rumina_habit": weekHabitData.WeekRuminaHabit,
  345. "week_chew_habit": weekHabitData.WeekChewHabit,
  346. "week_intake_habit": weekHabitData.WeekIntakeHabit,
  347. "week_inactive_habit": weekHabitData.WeekInactiveHabit,
  348. }).Error; err != nil {
  349. zaplog.Error("WeeklyUpdateActiveHabit",
  350. zap.Error(err),
  351. zap.Any("NeckActiveHabit", v),
  352. zap.Any("pastureId", pastureId),
  353. )
  354. }
  355. }
  356. if err = e.SumUpdateActiveHabit(pastureId, newNeckActiveHabitList, xToDay); err != nil {
  357. zaplog.Error("WeeklyUpdateActiveHabit",
  358. zap.Any("SumUpdateActiveHabit", err),
  359. zap.Any("newNeckActiveHabitList", newNeckActiveHabitList),
  360. zap.Any("pastureId", pastureId),
  361. )
  362. }
  363. if err = e.ActiveChange(pastureId, processIds, xToDay); err != nil {
  364. zaplog.Error("WeeklyUpdateActiveHabit",
  365. zap.Any("ActiveChange", err),
  366. zap.Any("processIds", processIds),
  367. zap.Any("xToDay", xToDay),
  368. zap.Any("pastureId", pastureId),
  369. )
  370. }
  371. return nil
  372. }
  373. // SumUpdateActiveHabit -- 累计24小时数值
  374. func (e *Entry) SumUpdateActiveHabit(pastureId int64, newNeckActiveHabitList []*model.NeckActiveHabit, xToDay *XToday) (err error) {
  375. for _, v := range newNeckActiveHabitList {
  376. sumHabitData := e.FindSumHabitData(pastureId, v.NeckRingNumber, v.HeatDate, v.Frameid, xToDay)
  377. // 更新过滤值
  378. if err = e.DB.Model(new(model.NeckActiveHabit)).
  379. Select("sum_rumina", "sum_intake", "sum_inactive", "sum_active", "sum_max_high", "sum_min_high", "sum_min_chew").
  380. Where("id = ?", v.Id).
  381. Updates(map[string]interface{}{
  382. "sum_rumina": sumHabitData.SumRumina,
  383. "sum_intake": sumHabitData.SumIntake,
  384. "sum_inactive": sumHabitData.SumInactive,
  385. "sum_active": sumHabitData.SumActive,
  386. "sum_max_high": sumHabitData.SumMaxHigh,
  387. "sum_min_high": sumHabitData.SumMinHigh,
  388. "sum_min_chew": sumHabitData.SumMinChew,
  389. }).Error; err != nil {
  390. zaplog.Error("WeeklyUpdateActiveHabit",
  391. zap.Any("err", err),
  392. zap.Any("NeckActiveHabit", v),
  393. zap.Any("pastureId", pastureId),
  394. )
  395. }
  396. }
  397. return err
  398. }
  399. // ActiveChange -- 变化百分比
  400. func (e *Entry) ActiveChange(pastureId int64, processIds []int64, xToDay *XToday) (err error) {
  401. newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  402. if err = e.DB.Model(new(model.NeckActiveHabit)).
  403. Where("id IN (?)", processIds).
  404. Where("week_high_habit > ?", 0).
  405. Where(e.DB.Where("high >= ?", xToDay.High).Or("rumina >= ?", xToDay.Rumina)).
  406. Find(&newNeckActiveHabitList).Error; err != nil {
  407. return xerr.WithStack(err)
  408. }
  409. for _, v := range newNeckActiveHabitList {
  410. highDiff := v.FilterHigh - v.WeekHighHabit
  411. denominator := float64(v.WeekHigh)*0.6 + float64(v.WeekHighHabit)*0.2 + float64(xToDay.WeeklyActive)*0.2
  412. if highDiff > 0 {
  413. v.ChangeHigh = int32(math.Round((float64(highDiff) / denominator / float64(v.WeekHighHabit)) * 100))
  414. } else {
  415. v.ChangeHigh = int32(math.Round(float64(highDiff) / float64(v.WeekHighHabit) * 100))
  416. }
  417. if v.WeekRuminaHabit != 0 {
  418. v.ChangeRumina = int32(math.Round(float64(v.FilterRumina-v.WeekRuminaHabit) / float64(v.WeekRuminaHabit) * 100))
  419. }
  420. if v.WeekChewHabit != 0 {
  421. v.ChangeChew = int32(math.Round(float64(v.FilterChew-v.WeekChewHabit) / float64(v.WeekChewHabit) * 100))
  422. }
  423. // 更新过滤值
  424. if err = e.DB.Model(new(model.NeckActiveHabit)).
  425. Select("change_high", "change_rumina", "change_chew").
  426. Where("id = ?", v.Id).
  427. Updates(map[string]interface{}{
  428. "change_high": v.ChangeHigh,
  429. "change_rumina": v.ChangeRumina,
  430. "change_chew": v.ChangeChew,
  431. }).Error; err != nil {
  432. zaplog.Error("WeeklyUpdateActiveHabit",
  433. zap.Error(err),
  434. zap.Any("NeckActiveHabit", v),
  435. zap.Any("pastureId", pastureId),
  436. )
  437. }
  438. }
  439. return err
  440. }
  441. func (e *Entry) WeeklyUpdateActiveHabitOld(pastureId int64, newNeckActiveHabitList []*model.NeckActiveHabit, xToDay *XToday) (err error) {
  442. for _, v := range newNeckActiveHabitList {
  443. // 前七天的
  444. weekHabitData := e.FindWeekHabitData(pastureId, v.NeckRingNumber, v.HeatDate, v.Frameid, xToDay)
  445. highDiff := v.FilterHigh - weekHabitData.WeekHighHabit
  446. denominator := float64(v.WeekHigh)*0.6 + float64(weekHabitData.WeekHighHabit)*0.2 + float64(xToDay.WeeklyActive)*0.2
  447. if highDiff > 0 {
  448. v.ChangeHigh = int32(math.Round((float64(highDiff) / denominator) * 100))
  449. } else {
  450. v.ChangeHigh = int32(math.Round(float64(highDiff) / denominator * 100))
  451. }
  452. if weekHabitData.WeekRuminaHabit != 0 {
  453. v.ChangeRumina = int32(math.Round(float64(v.FilterRumina-weekHabitData.WeekRuminaHabit) / float64(weekHabitData.WeekRuminaHabit) * 100))
  454. } else {
  455. v.ChangeRumina = 0
  456. }
  457. if weekHabitData.WeekChewHabit != 0 {
  458. v.ChangeChew = int32(math.Round(float64(v.FilterChew-weekHabitData.WeekChewHabit) / float64(weekHabitData.WeekChewHabit) * 100))
  459. } else {
  460. v.ChangeChew = 0
  461. }
  462. sumHabitData := e.FindSumHabitData(pastureId, v.NeckRingNumber, v.HeatDate, v.Frameid, xToDay)
  463. // 更新过滤值
  464. if err = e.DB.Model(new(model.NeckActiveHabit)).
  465. Select(
  466. "week_high_habit", "week_rumina_habit", "week_chew_habit", "week_intake_habit", "week_inactive_habit",
  467. "sum_rumina", "sum_intake", "sum_inactive", "sum_active", "sum_max_high", "sum_min_high", "sum_min_chew",
  468. "change_high", "change_rumina", "change_chew", "before_three_sum_rumina", "before_three_sum_intake",
  469. ).Where("id = ?", v.Id).
  470. Updates(map[string]interface{}{
  471. "week_high_habit": weekHabitData.WeekHighHabit,
  472. "week_rumina_habit": weekHabitData.WeekRuminaHabit,
  473. "week_chew_habit": weekHabitData.WeekChewHabit,
  474. "week_intake_habit": weekHabitData.WeekIntakeHabit,
  475. "week_inactive_habit": weekHabitData.WeekInactiveHabit,
  476. "sum_rumina": sumHabitData.SumRumina,
  477. "sum_intake": sumHabitData.SumIntake,
  478. "sum_inactive": sumHabitData.SumInactive,
  479. "sum_active": sumHabitData.SumActive,
  480. "sum_max_high": sumHabitData.SumMaxHigh,
  481. "sum_min_high": sumHabitData.SumMinHigh,
  482. "sum_min_chew": sumHabitData.SumMinChew,
  483. "change_high": v.ChangeHigh,
  484. "change_rumina": v.ChangeRumina,
  485. "change_chew": v.ChangeChew,
  486. }).Error; err != nil {
  487. zaplog.Error("WeeklyUpdateActiveHabit",
  488. zap.Error(err),
  489. zap.Any("NeckActiveHabit", v),
  490. zap.Any("pastureId", pastureId),
  491. )
  492. }
  493. }
  494. return err
  495. }
  496. func (e *Entry) Before3DaysNeckActiveHabit(pastureId int64, processIds []int64, xToDay *XToday) (err error) {
  497. newNeckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  498. if err = e.DB.Model(new(model.NeckActiveHabit)).
  499. Where("id IN (?)", processIds).
  500. Order("heat_date,neck_ring_number,frameid").
  501. Find(&newNeckActiveHabitList).Error; err != nil {
  502. return xerr.WithStack(err)
  503. }
  504. for _, v := range newNeckActiveHabitList {
  505. before3DaysNeckActiveHabit := e.FindBefore3DaysNeckActiveHabit(pastureId, v.NeckRingNumber, v.HeatDate, v.Frameid)
  506. // 更新过滤值
  507. if err = e.DB.Model(new(model.NeckActiveHabit)).
  508. Select("before_three_sum_rumina", "before_three_sum_intake").
  509. Where("id = ?", v.Id).
  510. Updates(map[string]interface{}{
  511. "before_three_sum_rumina": before3DaysNeckActiveHabit.SumRumina,
  512. "before_three_sum_intake": before3DaysNeckActiveHabit.SumIntake,
  513. }).Error; err != nil {
  514. zaplog.Error("Before3DaysNeckActiveHabit",
  515. zap.Error(err),
  516. zap.Any("NeckActiveHabit", v),
  517. zap.Any("pastureId", pastureId),
  518. )
  519. }
  520. }
  521. return nil
  522. }
  523. // SecondUpdateChangeFilter 第二次更新变化趋势滤波
  524. func (e *Entry) SecondUpdateChangeFilter(pastureId int64, xToday *XToday) (err error) {
  525. newChangeFilterList := make([]*ChangeFilterData, 0)
  526. if err = e.DB.Model(new(model.NeckActiveHabit)).
  527. Select("id", "neck_ring_number", "change_high", "change_filter", "rumina_filter", "change_rumina",
  528. "chew_filter", "change_chew", "heat_date", "frameid", "IF(lact = 0, 0.8, 1) as xlc_dis_count").
  529. Where("id > ?", xToday.LastMaxHabitId).
  530. Where("heat_date >= ?", time.Now().AddDate(0, 0, -2).Format(model.LayoutDate2)).
  531. Where("pasture_id = ?", pastureId).
  532. Where("change_filter = ?", model.InitChangeFilter).
  533. Where("change_high > ?", MinChangeHigh).
  534. Order("neck_ring_number,heat_date,frameid").
  535. Limit(int(defaultLimit)).
  536. Find(&newChangeFilterList).Error; err != nil {
  537. return xerr.WithStack(err)
  538. }
  539. for _, v := range newChangeFilterList {
  540. secondFilterData := &SecondFilterData{}
  541. frameId := v.FrameId
  542. heatDate := v.HeatDate
  543. if v.FrameId == 0 {
  544. frameId = 11
  545. heatDateParse, _ := time.Parse(model.LayoutDate2, heatDate)
  546. heatDate = heatDateParse.AddDate(0, 0, -1).Format(model.LayoutDate2)
  547. }
  548. if err = e.DB.Model(new(model.NeckActiveHabit)).
  549. Select("neck_ring_number", "filter_high", "filter_rumina", "filter_chew").
  550. Where("neck_ring_number = ?", v.NeckRingNumber).
  551. Where("heat_date = ?", heatDate).
  552. Where("frameid = ?", frameId).
  553. First(&secondFilterData).Error; err != nil {
  554. zaplog.Error("EntryUpdateActiveHabit", zap.Any("FirstFilterUpdate", err))
  555. }
  556. if v.ChangeFilter > MinChangeFilter {
  557. secondFilterData.ChangeFilter = float64(v.ChangeFilter)
  558. } else {
  559. if v.NeckRingNumber == secondFilterData.NeckRingNumber {
  560. secondFilterData.ChangeFilter = secondFilterData.ChangeFilter*(1-(float64(xToday.XChangeDiscount)/10)*v.XlcDisCount) +
  561. math.Min(float64(v.ChangeHigh), secondFilterData.ChangeFilter+135)*(float64(xToday.XChangeDiscount)/10)*v.XlcDisCount
  562. } else {
  563. secondFilterData.ChangeFilter = 0
  564. }
  565. }
  566. if v.RuminaFilter > MinRuminaFilter {
  567. secondFilterData.RuminaFilter = float64(v.ChangeFilter)
  568. } else {
  569. if v.NeckRingNumber == secondFilterData.NeckRingNumber {
  570. discount := float64(xToday.XRuminaDisc) / 10 * v.XlcDisCount
  571. if math.Abs(float64(v.ChangeRumina)) > 60 {
  572. discount *= 0.5
  573. }
  574. secondFilterData.RuminaFilter = secondFilterData.RuminaFilter*(1-discount) + float64(v.ChangeRumina)*discount
  575. } else {
  576. secondFilterData.RuminaFilter = 0
  577. }
  578. }
  579. secondFilterData.RuminaFilter = math.Min(50, secondFilterData.RuminaFilter)
  580. if v.ChewFilter > MinChewFilter {
  581. secondFilterData.ChewFilter = float64(v.ChangeChew)
  582. } else {
  583. if v.NeckRingNumber == secondFilterData.NeckRingNumber {
  584. discount := float64(xToday.XRuminaDisc) / 10
  585. if math.Abs(float64(v.ChangeChew)) > 60 {
  586. discount *= 0.5
  587. }
  588. secondFilterData.ChewFilter = secondFilterData.ChewFilter*(1-discount) + float64(v.ChangeChew)*discount
  589. } else {
  590. secondFilterData.ChewFilter = 0
  591. }
  592. }
  593. secondFilterData.ChewFilter = math.Min(50, secondFilterData.ChewFilter)
  594. if err = e.DB.Model(new(model.NeckActiveHabit)).
  595. Select("change_filter", "rumina_filter", "chew_filter").
  596. Where("id = ?", v.Id).
  597. Updates(map[string]interface{}{
  598. "change_filter": secondFilterData.ChangeFilter,
  599. "rumina_filter": secondFilterData.RuminaFilter,
  600. "chew_filter": secondFilterData.ChewFilter,
  601. }).Error; err != nil {
  602. zaplog.Error("SecondUpdateChangeFilter-1", zap.Any("error", err), zap.Any("xToday", xToday))
  603. }
  604. }
  605. return nil
  606. }
  607. // FilterCorrectAndScoreUpdate 计算活动量变化趋势校正值(活跃度校正)和健康评分
  608. func (e *Entry) FilterCorrectAndScoreUpdate(pastureId int64, xToday *XToday) error {
  609. beginDayDate := time.Now()
  610. before7DayDate := beginDayDate.AddDate(0, 0, -7).Format(model.LayoutDate2)
  611. before1DayDate := beginDayDate.AddDate(0, 0, -1).Format(model.LayoutDate2)
  612. activityVolumeList := make([]*ActivityVolume, 0)
  613. activityVolumeMap := make(map[string]*ActivityVolume)
  614. if err := e.DB.Model(new(model.NeckActiveHabit)).
  615. Select("neck_ring_number", "AVG(IF(change_filter>=60, 60, change_filter)) as avg_filter",
  616. "ROUND(STD(IF(change_filter>=60, 60, change_filter))) as std_filter", "COUNT(1) as nb").
  617. Where("heat_date BETWEEN ? AND ?", before7DayDate, before1DayDate).
  618. Where("pasture_id = ?", pastureId).
  619. Where(e.DB.Where("high > ?", xToday.High).Or("rumina >= ?", xToday.Rumina)).
  620. Where("active_time <= ?", beginDayDate.Add(-12*time.Hour).Format(model.LayoutTime)).
  621. Where("change_filter > ?", MinChangeFilter).
  622. Having("nb > ?", DefaultNb).
  623. Group("neck_ring_number").
  624. Find(&activityVolumeList).Error; err != nil {
  625. zaplog.Error("ActivityVolumeChanges-0", zap.Any("error", err), zap.Any("xToday", xToday))
  626. }
  627. if len(activityVolumeList) > 0 {
  628. for _, v := range activityVolumeList {
  629. activityVolumeMap[v.NeckRingNumber] = v
  630. }
  631. }
  632. neckActiveHabitList := make([]*model.NeckActiveHabit, 0)
  633. if err := e.DB.Model(new(model.NeckActiveHabit)).
  634. Where("id <= ?", xToday.CurrMaxHabitId).
  635. Where("heat_date >= ?", before1DayDate).
  636. Where("pasture_id = ?", pastureId).
  637. Where(e.DB.Where("high > ?", xToday.High).Or("rumina > ?", xToday.Rumina)).
  638. Find(&neckActiveHabitList).Error; err != nil {
  639. zaplog.Error("ActivityVolumeChanges-1", zap.Any("error", err), zap.Any("xToday", xToday))
  640. return xerr.WithStack(err)
  641. }
  642. for _, v := range neckActiveHabitList {
  643. if filterCorrectMap, ok := activityVolumeMap[v.NeckRingNumber]; ok {
  644. filterCorrect := model.DefaultFilterCorrect - int(math.Floor(filterCorrectMap.AvgFilter/3+float64(filterCorrectMap.StdFilter)/2))
  645. // 活动量校正系数
  646. if err := e.DB.Model(new(model.NeckActiveHabit)).
  647. Where("id = ?", v.Id).
  648. Where("neck_ring_number = ?", v.NeckRingNumber).
  649. Update("filter_correct", filterCorrect).Error; err != nil {
  650. zaplog.Error("ActivityVolumeChanges-2", zap.Any("error", err), zap.Any("xToday", xToday))
  651. continue
  652. }
  653. }
  654. cowScore := calculateScore(v)
  655. if err := e.DB.Model(new(model.NeckActiveHabit)).
  656. Where("id = ?", v.Id).
  657. Update("score", cowScore).Error; err != nil {
  658. zaplog.Error("ActivityVolumeChanges-2", zap.Any("error", err), zap.Any("xToday", xToday))
  659. continue
  660. }
  661. }
  662. return nil
  663. }
  664. // UpdateChangeAdJust 更新群体校正数据
  665. func (e *Entry) UpdateChangeAdJust(pastureId int64, xToday *XToday) error {
  666. /*-- 插入群体校正表
  667. INSERT INTO data_bar_change(heatdate, frameid, intCurBar, intCurBarName, nb, highchange, changefilter)
  668. SELECT h.heatdate, h.frameid, c.intCurBar, c.intCurBarName, COUNT(*) nb, ROUND(AVG(h.highchange)) highchange, ROUND(AVG(h.changefilter) ) changefilter
  669. FROM h_activehabit h JOIN cow c ON h.intCowId=c.intCowId
  670. WHERE h.heatdate>=(CURDATE() -INTERVAL 1 DAY )
  671. GROUP BY h.heatdate, h.frameid, c.intCurBar
  672. ORDER BY h.heatdate, h.frameid, c.intCurBarName
  673. ON DUPLICATE KEY UPDATE nb = VALUES(nb), highchange = VALUES(highchange), changefilter = VALUES(changefilter);
  674. UPDATE h_activehabit h
  675. JOIN cow c ON h.intCowId=c.intCowId
  676. JOIN data_bar_change cg ON h.heatdate=cg.heatdate AND h.frameid=cg.frameid AND c.intCurBar=cg.intCurBar
  677. SET h.changeadjust=cg.changefilter
  678. WHERE h.id>xBeg_update_act_Id AND h.heatdate>=CURRENT_DATE() - INTERVAL 1 DAY AND ABS(cg.changefilter)>=10;
  679. */
  680. res := make([]*model.NeckRingBarChange, 0)
  681. oneDayAgo := time.Now().AddDate(0, 0, -1).Format(model.LayoutDate2)
  682. if err := e.DB.Table(fmt.Sprintf("%s as h", new(model.NeckActiveHabit).TableName())).
  683. 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").
  684. Joins("JOIN cow as c ON h.neck_ring_number = c.neck_ring_number").
  685. Where("h.pasture_id = ?", pastureId).
  686. Where("h.heat_date >= ?", oneDayAgo).
  687. Where("h.cow_id >= ?", 0).
  688. Where("h.cow_id >= ?", 0).
  689. Group("h.heat_date, h.frameid, c.pen_id").
  690. Order("h.heat_date, h.frameid, c.pen_name").
  691. Find(&res).Error; err != nil {
  692. return xerr.WithStack(err)
  693. }
  694. // todo ABS(cg.changefilter)>=10;
  695. for _, v := range res {
  696. if err := e.DB.Model(new(model.NeckActiveHabit)).
  697. Where("id > ?", xToday.LastMaxHabitId).
  698. Where("heat_date = ?", v.HeatDate).
  699. Where("frameid = ?", v.FrameId).
  700. Where("neck_ring_number = ?", v.NeckRingNumber).
  701. Update("change_adjust", v.ChangeHigh).Error; err != nil {
  702. zaplog.Error("UpdateChangeAdJust-1", zap.Any("error", err), zap.Any("xToday", xToday))
  703. }
  704. }
  705. return nil
  706. }
  707. func (e *Entry) UpdateNeckRingOriginalIsShow(habit *model.NeckActiveHabit) error {
  708. if err := e.DB.Model(new(model.NeckRingOriginal)).
  709. Where("pasture_id = ?", habit.PastureId).
  710. Where("neck_ring_number = ?", habit.NeckRingNumber).
  711. Where("active_date = ?", habit.HeatDate).
  712. Where("frameid IN (?)", util.FrameIds(habit.Frameid)).
  713. Update("is_show", pasturePb.IsShow_Ok).Error; err != nil {
  714. return xerr.WithStack(err)
  715. }
  716. return nil
  717. }
  718. // RemoveDuplicates 清洗一下数据,去掉重复的,如果有重复的,取最新的一条数据
  719. func RemoveDuplicates(records []*model.NeckRingOriginal) []*model.NeckRingOriginal {
  720. uniqueRecords := make(map[string]*model.NeckRingOriginal)
  721. // 遍历原始数组
  722. for _, record := range records {
  723. mapKey := fmt.Sprintf("%s%s%s%s%d", record.NeckRingNumber, model.JoinKey, record.ActiveDate, model.JoinKey, record.Frameid) // 0001/2023-12-04/0 0001/2023-12-03/4
  724. if existing, exists := uniqueRecords[mapKey]; exists {
  725. if record.CreatedAt > existing.CreatedAt {
  726. uniqueRecords[mapKey] = record
  727. }
  728. } else {
  729. uniqueRecords[mapKey] = record
  730. }
  731. }
  732. // 将 map 中的记录转换为切片
  733. result := make([]*model.NeckRingOriginal, 0, len(uniqueRecords))
  734. for _, record := range uniqueRecords {
  735. result = append(result, record)
  736. }
  737. return result
  738. }
  739. // Recalculate 合并计算
  740. func Recalculate(neckRingList []*model.NeckRingOriginal) []*model.NeckActiveHabit {
  741. originalMapData := make(map[string]*model.NeckRingOriginalMerge)
  742. // 合并成2个小时的
  743. for _, v := range neckRingList {
  744. xframeId := util.XFrameId(v.Frameid)
  745. mapKey := fmt.Sprintf("%s%s%s%s%d", v.NeckRingNumber, model.JoinKey, v.ActiveDate, model.JoinKey, xframeId) // 0001/2023-12-04/0 0001/2023-12-03/4
  746. if originalMapData[mapKey] == nil {
  747. originalMapData[mapKey] = new(model.NeckRingOriginalMerge)
  748. }
  749. originalMapData[mapKey].IsMageData(v, xframeId)
  750. }
  751. currTime := time.Now()
  752. res := make([]*model.NeckActiveHabit, 0)
  753. // 算平均值
  754. for k, v := range originalMapData {
  755. // 过滤掉合并后<6条数据,如果时间太短就晚点再算
  756. if v.RecordCount < model.DefaultRecordCount {
  757. currMaxXframeId := util.FrameIdMapReverse[int32(currTime.Hour())]
  758. activeDateString := fmt.Sprintf("%s %02d:00:00", v.ActiveDate, v.XframeId*2+1)
  759. activeDate, _ := time.Parse(model.LayoutTime, activeDateString)
  760. if currMaxXframeId-v.XframeId <= 1 && currTime.Add(-1*time.Hour).Unix() < activeDate.Unix() {
  761. delete(originalMapData, k)
  762. continue
  763. }
  764. }
  765. v.SumAvg()
  766. }
  767. if len(originalMapData) <= 0 {
  768. return res
  769. }
  770. res = model.NeckRingOriginalMap(originalMapData).ForMatData()
  771. sort.Sort(model.NeckActiveHabitSlice(res))
  772. return res
  773. }
  774. func (e *Entry) againRecalculate(data *model.NeckActiveHabit) *model.NeckActiveHabit {
  775. originalList := make([]*model.NeckRingOriginal, 0)
  776. frameIds := util.FrameIds(data.Frameid)
  777. sql := ""
  778. for _, frameId := range frameIds {
  779. sql += fmt.Sprintf(`SELECT * FROM neck_ring_original WHERE pasture_id = %d AND neck_ring_number = '%s' AND active_date = '%s' AND frameid = %d UNION ALL `, data.PastureId, data.NeckRingNumber, data.HeatDate, frameId)
  780. }
  781. if len(sql) > 0 {
  782. sql = strings.TrimSuffix(sql, "UNION ALL ")
  783. }
  784. if err := e.DB.Raw(sql).Find(&originalList).Error; err != nil {
  785. return nil
  786. }
  787. /*if err := e.DB.Model(new(model.NeckRingOriginal)).
  788. Where("pasture_id = ?", data.PastureId).
  789. Where("neck_ring_number = ?", data.NeckRingNumber).
  790. Where("active_date = ?", data.HeatDate).
  791. Where("frameid IN (?)", frameIds).
  792. Find(&originalList).Error; err != nil {
  793. return nil
  794. }*/
  795. originalList = RemoveDuplicates(originalList)
  796. newDataList := Recalculate(originalList)
  797. if len(newDataList) != 1 {
  798. return nil
  799. }
  800. res := newDataList[0]
  801. res.IsShow = pasturePb.IsShow_No
  802. return res
  803. }
  804. // computeIfPositiveElse 辅助函数来计算过滤值
  805. func computeIfPositiveElse(newValue, prevFilterValue float64, weightPrev, weightNew float64) float64 {
  806. return math.Ceil((prevFilterValue * weightPrev) + (weightNew * newValue))
  807. }
  808. // 计算 score 的逻辑
  809. func calculateScore(habit *model.NeckActiveHabit) int {
  810. // 第一部分逻辑
  811. var part1 float64
  812. switch {
  813. case (habit.CalvingAge <= 1 && habit.Lact >= 1) ||
  814. (habit.CalvingAge >= 2 && habit.CalvingAge <= 13 && (habit.SumRumina+habit.SumIntake) == 0) ||
  815. ((habit.Lact == 0 || habit.CalvingAge >= 14) && habit.ChangeFilter == -99):
  816. part1 = -199
  817. case habit.CalvingAge >= 2 && habit.CalvingAge <= 13:
  818. part1 = math.Min((float64(habit.SumRumina+habit.SumIntake)-(100+math.Min(7, float64(habit.CalvingAge))*60))/10*2, 0)
  819. case habit.ChangeFilter > -99:
  820. part1 = math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinHigh), 0)))*0.2 +
  821. math.Min(0, math.Min(getValueOrDefault(float64(habit.ChangeFilter), 0), getValueOrDefault(float64(habit.SumMinChew), 0)))*0.2 +
  822. getRuminaSumIntakeSumScore(float64(habit.SumRumina+habit.SumIntake)) + getAdditionalScore(habit)
  823. default:
  824. part1 = -299
  825. }
  826. // 第二部分逻辑
  827. var part2 float64
  828. switch {
  829. case habit.FirmwareVersion%100 >= 52:
  830. part2 = 1
  831. case habit.FirmwareVersion%100 >= 30 && habit.FirmwareVersion%100 <= 43:
  832. part2 = 0.8
  833. default:
  834. part2 = 0.6
  835. }
  836. // 最终 score
  837. return DefaultScore + int(math.Floor(part1*part2))
  838. }
  839. // 获取值或默认值
  840. func getValueOrDefault(value, defaultValue float64) float64 {
  841. if value > -99 {
  842. return value
  843. }
  844. return defaultValue
  845. }
  846. // 计算累计反刍得分
  847. func getRuminaSumIntakeSumScore(sum float64) float64 {
  848. switch {
  849. case sum < 80:
  850. return -30
  851. case sum < 180:
  852. return -20
  853. case sum < 280:
  854. return -10
  855. default:
  856. return 0
  857. }
  858. }
  859. // 计算额外得分
  860. func getAdditionalScore(habit *model.NeckActiveHabit) float64 {
  861. var score float64
  862. if (habit.SumRumina+habit.SumIntake < 280 || habit.SumMinHigh+habit.SumMinChew < -50) && habit.SumMaxHigh > 50 {
  863. score += 10
  864. }
  865. if habit.ChangeFilter < -30 && habit.ChangeFilter <= habit.SumMinHigh && habit.ChewFilter < -30 && habit.ChewFilter <= habit.SumMinChew {
  866. score -= 5
  867. }
  868. return score
  869. }