assessment_form.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. package services
  2. import (
  3. "eta/eta_api/models"
  4. "eta/eta_api/utils"
  5. "fmt"
  6. "math"
  7. "sync"
  8. "time"
  9. )
  10. // 填报单单号锁
  11. var (
  12. formCodeMutex sync.Mutex
  13. formCodeUnique sync.Map
  14. formCodeLastDate string
  15. )
  16. // GenerateAssessmentFormCode 生成单号
  17. func GenerateAssessmentFormCode() (formCode string, err error) {
  18. formCodeMutex.Lock()
  19. defer formCodeMutex.Unlock()
  20. currentDate := time.Now().Format("20060102")
  21. maxAttempts := 1000
  22. baseNumber := 1
  23. // 检查日期变化,清空map
  24. if formCodeLastDate != "" && formCodeLastDate != currentDate {
  25. formCodeUnique = sync.Map{}
  26. }
  27. formCodeLastDate = currentDate
  28. // 获取单号最大ID
  29. formOb := new(models.AssessmentForm)
  30. maxForm, e := formOb.GetItemByCondition(``, make([]interface{}, 0), fmt.Sprintf("%s DESC", formOb.Cols().PrimaryId))
  31. if e != nil && !utils.IsErrNoRow(e) {
  32. err = fmt.Errorf("获取最大单号失败, %v", e)
  33. return
  34. }
  35. if maxForm != nil && maxForm.AssessmentFormId > 0 {
  36. baseNumber = maxForm.AssessmentFormId + 1
  37. }
  38. // 尝试生成自增ID
  39. for i := 0; i < maxAttempts; i++ {
  40. serialNumber := fmt.Sprintf("%06d", baseNumber+i)
  41. uniqueCode := fmt.Sprintf("GDKH%s%s", currentDate, serialNumber)
  42. if _, loaded := formCodeUnique.LoadOrStore(uniqueCode, true); loaded {
  43. continue
  44. }
  45. // 检查单号是否已存在
  46. cond := fmt.Sprintf(` AND %s = ?`, formOb.Cols().FormCode)
  47. pars := make([]interface{}, 0)
  48. pars = append(pars, uniqueCode)
  49. exists, e := formOb.GetCountByCondition(cond, pars)
  50. if e != nil {
  51. formCodeUnique.Delete(uniqueCode)
  52. err = fmt.Errorf("检查单号是否存在失败, %v", e)
  53. return
  54. }
  55. if exists <= 0 {
  56. formCode = uniqueCode
  57. return
  58. }
  59. }
  60. err = fmt.Errorf("生成单号失败, 尝试次数超过%d次", maxAttempts)
  61. return
  62. }
  63. // GetAssessmentWeekAndFriday 获取给定日期的周数,以及所在周的周五
  64. // 规则:某年的第一个周五所在周为该年的第一周
  65. // 返回:格式化字符串(如"202504")、周数(如4)和所在周的周五日期
  66. func GetAssessmentWeekAndFriday(t time.Time) (string, int, time.Time) {
  67. year := t.Year()
  68. // 找到该年第一个周五的日期
  69. firstFriday := findFirstFriday(year)
  70. // 计算当前日期与该年第一个周五所在周的第一天(周一)的天数差
  71. daysSinceFirstWeek := t.YearDay() - firstFriday.YearDay() + int(firstFriday.Weekday()) - int(time.Monday)
  72. weekNum := 1
  73. if daysSinceFirstWeek > 0 {
  74. weekNum += daysSinceFirstWeek / 7
  75. if daysSinceFirstWeek%7 != 0 {
  76. weekNum++
  77. }
  78. } else {
  79. // 如果当前日期在第一个周五所在周之前,则属于上一年的最后一周
  80. prevYear := year - 1
  81. prevFirstFriday := findFirstFriday(prevYear)
  82. daysInPrevYear := daysInYear(prevYear)
  83. daysSincePrevFirstWeek := t.YearDay() + (daysInPrevYear - prevFirstFriday.YearDay()) + int(prevFirstFriday.Weekday()) - int(time.Monday)
  84. weekNum = daysSincePrevFirstWeek / 7
  85. if daysSincePrevFirstWeek%7 != 0 {
  86. weekNum++
  87. }
  88. year = prevYear
  89. }
  90. // 计算当前日期所在周的周五
  91. currentWeekFriday := findFridayOfWeek(t)
  92. // 格式化输出
  93. formatted := fmt.Sprintf("%04d%02d", year, weekNum)
  94. return formatted, weekNum, currentWeekFriday
  95. }
  96. // findFirstFriday 找到某年的第一个周五
  97. func findFirstFriday(year int) time.Time {
  98. // 从1月1日开始找
  99. date := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
  100. // 找到第一个周五
  101. for date.Weekday() != time.Friday {
  102. date = date.AddDate(0, 0, 1)
  103. }
  104. return date
  105. }
  106. // findFridayOfWeek 找到给定日期所在周的周五
  107. func findFridayOfWeek(t time.Time) time.Time {
  108. // 获取当前日期是周几 (0=周日, 1=周一, ..., 6=周六)
  109. weekday := int(t.Weekday())
  110. // 计算到周五的天数差 (周五是5)
  111. daysToFriday := (5 - weekday + 7) % 7
  112. // 如果当前就是周五,daysToFriday会是0
  113. if daysToFriday < 0 {
  114. daysToFriday += 7
  115. }
  116. return t.AddDate(0, 0, daysToFriday)
  117. }
  118. // daysInYear 计算某年有多少天
  119. func daysInYear(year int) int {
  120. first := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
  121. last := time.Date(year, time.December, 31, 0, 0, 0, 0, time.UTC)
  122. return last.YearDay() - first.YearDay() + 1
  123. }
  124. // CheckAssessmentFormButton 校验填报单按钮
  125. func CheckAssessmentFormButton(item *models.AssessmentForm, sysAdminId int, authResearcherIds []int) (button models.AssessmentFormButton) {
  126. if item == nil || sysAdminId <= 0 {
  127. return
  128. }
  129. // 已提交状态
  130. if item.Status == models.AssessmentFormStatusSubmitted {
  131. if sysAdminId == item.ResearcherAdminId || utils.InArrayByInt(authResearcherIds, item.ResearcherId) {
  132. button.ViewButton = true
  133. }
  134. // 本周结束前可撤销(TODO:这里象屿会有一个外部条件:当周周报未提交,暂不处理视作true)
  135. if sysAdminId == item.ResearcherAdminId && !item.WeekEnd.IsZero() && time.Now().Local().Before(item.WeekEnd) {
  136. button.CancelButton = true
  137. }
  138. }
  139. // 待提交状态(仅自己有操作按钮)
  140. if item.Status == models.AssessmentFormStatusDraft && sysAdminId == item.ResearcherAdminId {
  141. button.EditButton = true
  142. button.RemoveButton = true
  143. // 本周结束前可提交
  144. if !item.WeekEnd.IsZero() && time.Now().Local().Before(item.WeekEnd) {
  145. button.SubmitButton = true
  146. }
  147. }
  148. return
  149. }
  150. // GetVarietyPriceAndForecastComment 获取品种价格详情及观点评价
  151. func GetVarietyPriceAndForecastComment(forms []*models.AssessmentForm) (varietyPrice []*models.AssessmentFormVarietyPrice, forecastComment []*models.AssessmentFormForecastComment, err error) {
  152. if len(forms) == 0 {
  153. return
  154. }
  155. baseDate := forms[0].BaseDate
  156. var varietyIds []int
  157. for _, v := range forms {
  158. varietyIds = append(varietyIds, v.VarietyId)
  159. }
  160. if len(varietyIds) == 0 || baseDate.IsZero() {
  161. return
  162. }
  163. // 获取品种信息
  164. varietyOb := new(models.AssessmentVariety)
  165. varietyMatch := make(map[int]*models.AssessmentVariety)
  166. {
  167. cond := fmt.Sprintf(` AND %s IN (?)`, varietyOb.Cols().PrimaryId)
  168. pars := make([]interface{}, 0)
  169. pars = append(pars, varietyIds)
  170. list, e := varietyOb.GetItemsByCondition(cond, pars, []string{}, "")
  171. if e != nil {
  172. err = fmt.Errorf("获取品种信息失败, %v", e)
  173. return
  174. }
  175. for _, v := range list {
  176. varietyMatch[v.AssessmentVarietyId] = v
  177. }
  178. }
  179. varietyLatestData := make(map[int]*models.AssessmentVarietyData)
  180. varietyBaseDateData := make(map[int]*models.AssessmentVarietyData)
  181. varietyNextWeekData := make(map[int]*models.AssessmentVarietyData)
  182. varietyNextMonthData := make(map[int]*models.AssessmentVarietyData)
  183. // 获取最新日期和数据
  184. dataOb := new(models.AssessmentVarietyData)
  185. latestData, e := dataOb.GetVarietyMaxDateData(varietyIds)
  186. if e != nil {
  187. err = fmt.Errorf("获取品种最新数据失败, %v", e)
  188. return
  189. }
  190. for _, v := range latestData {
  191. varietyLatestData[v.VarietyId] = v
  192. }
  193. // 获取基准日期N至N+4周的数据
  194. var dateArr []string
  195. nextWeek := baseDate.AddDate(0, 0, 7)
  196. nextMonth := baseDate.AddDate(0, 0, 28)
  197. dateArr = append(dateArr, baseDate.Format(utils.FormatDate), nextWeek.Format(utils.FormatDate), nextMonth.Format(utils.FormatDate))
  198. {
  199. cond := fmt.Sprintf(` AND %s IN (?) AND %s IN (?)`, dataOb.Cols().VarietyId, dataOb.Cols().WeekDate)
  200. pars := make([]interface{}, 0)
  201. pars = append(pars, varietyIds, dateArr)
  202. list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, "")
  203. if e != nil {
  204. err = fmt.Errorf("获取基准日至N+4周数据失败, %v", e)
  205. return
  206. }
  207. for _, v := range list {
  208. if varietyMatch[v.VarietyId] == nil {
  209. continue
  210. }
  211. if baseDate.Equal(v.WeekDate) {
  212. varietyBaseDateData[v.VarietyId] = v
  213. continue
  214. }
  215. if baseDate.AddDate(0, 0, 7).Equal(v.WeekDate) {
  216. varietyNextWeekData[v.VarietyId] = v
  217. continue
  218. }
  219. if baseDate.AddDate(0, 0, 28).Equal(v.WeekDate) {
  220. varietyNextMonthData[v.VarietyId] = v
  221. continue
  222. }
  223. }
  224. }
  225. varietyPrice = make([]*models.AssessmentFormVarietyPrice, 0)
  226. forecastComment = make([]*models.AssessmentFormForecastComment, 0)
  227. for _, v := range forms {
  228. vat := varietyMatch[v.VarietyId]
  229. if vat == nil {
  230. utils.FileLog.Info(fmt.Sprintf("GetVarietyPriceAndForecastComment, 品种不存在: %d", v.VarietyId))
  231. continue
  232. }
  233. // 品种价格评价
  234. vp := new(models.AssessmentFormVarietyPrice)
  235. vp.VarietyId = v.VarietyId
  236. vp.VarietyCode = v.VarietyCode
  237. vp.VarietyName = v.VarietyName
  238. if varietyLatestData[v.VarietyId] != nil {
  239. vp.EndDate = varietyLatestData[v.VarietyId].WeekDate.Format(utils.FormatDate)
  240. vp.LatestValue = fmt.Sprint(varietyLatestData[v.VarietyId].CloseValue)
  241. }
  242. var (
  243. baseDataPrice *float64
  244. nextMonthPrice *float64
  245. nextWeekHighPrice *float64
  246. nextWeekLowPrice *float64
  247. )
  248. if varietyBaseDateData[v.VarietyId] != nil {
  249. baseDataPrice = &varietyBaseDateData[v.VarietyId].CloseValue
  250. vp.BaseDatePrice = fmt.Sprint(varietyBaseDateData[v.VarietyId].CloseValue)
  251. }
  252. if varietyNextWeekData[v.VarietyId] != nil {
  253. nextWeekHighPrice = &varietyNextWeekData[v.VarietyId].HighValue
  254. nextWeekLowPrice = &varietyNextWeekData[v.VarietyId].LowValue
  255. vp.NextWeekPrice = fmt.Sprint(varietyNextWeekData[v.VarietyId].CloseValue)
  256. }
  257. if varietyNextMonthData[v.VarietyId] != nil {
  258. nextMonthPrice = &varietyNextMonthData[v.VarietyId].CloseValue
  259. vp.NextMonthPrice = fmt.Sprint(varietyNextMonthData[v.VarietyId].CloseValue)
  260. }
  261. varietyPrice = append(varietyPrice, vp)
  262. // 观点评价
  263. fc := new(models.AssessmentFormForecastComment)
  264. fc.VarietyId = v.VarietyId
  265. fc.VarietyCode = v.VarietyCode
  266. fc.VarietyName = v.VarietyName
  267. fc.WeekTime = v.WeekTime
  268. if !v.SubmitTime.IsZero() {
  269. fc.SubmitTime = v.SubmitTime.Format(utils.FormatDateUnSpace)
  270. }
  271. // 月度涨跌评价
  272. if baseDataPrice != nil && nextMonthPrice != nil {
  273. _, tips, right := calculateMonthlyPriceTrend(*baseDataPrice, *nextMonthPrice, vat.MonthlyFluctuate, v.MonthlyPriceForecast)
  274. fc.MonthlyPriceComment = tips
  275. fc.MonthlyPriceForecastRight = right
  276. }
  277. // 周度上下行风险
  278. if baseDataPrice != nil && nextWeekHighPrice != nil {
  279. _, tips, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekHighPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, true)
  280. fc.WeeklyUpComment = tips
  281. fc.WeeklyUpForecastRight = right
  282. }
  283. if baseDataPrice != nil && nextWeekLowPrice != nil {
  284. _, tips, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekLowPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, false)
  285. fc.WeeklyDownComment = tips
  286. fc.WeeklyDownForecastRight = right
  287. }
  288. forecastComment = append(forecastComment, fc)
  289. }
  290. return
  291. }
  292. // calculateMonthly 判断月度涨跌趋势
  293. func calculateMonthlyPriceTrend(basePrice, monthPrice, monthlyFluctuate float64, forecast string) (result, tips string, right bool) {
  294. if basePrice <= 0 || monthPrice <= 0 || monthlyFluctuate <= 0 {
  295. return
  296. }
  297. // 计算月度价格变化比例:月度价格/当周价格-1
  298. percent := (monthPrice/basePrice - 1) * 100
  299. // 判断价格趋势
  300. switch {
  301. case percent > monthlyFluctuate:
  302. result = models.AssessmentFormMonthlyPriceUp
  303. case percent < -monthlyFluctuate:
  304. result = models.AssessmentFormMonthlyPriceDown
  305. default:
  306. result = models.AssessmentFormMonthlyPriceShake
  307. }
  308. // 如果有进行预测,判断是否正确,返回提示语句
  309. if forecast == "" {
  310. return
  311. }
  312. tips = fmt.Sprintf("判断 %s,实际 %s", forecast, result)
  313. if forecast == result {
  314. right = true
  315. }
  316. return
  317. }
  318. // calculateWeekUpDownTrend 判断周度上下行风险
  319. func calculateWeekUpDownTrend(basePrice, weekPrice, weekFluctuate float64, forecast string, calculateUp bool) (result, tips string, right bool) {
  320. if basePrice <= 0 || weekPrice <= 0 || weekFluctuate <= 0 {
  321. return
  322. }
  323. percent := (weekPrice/basePrice - 1) * 100
  324. // 上下行
  325. result = models.AssessmentFormWeekUpNo
  326. if calculateUp {
  327. if percent > weekFluctuate {
  328. result = models.AssessmentFormWeekUpYes
  329. }
  330. } else {
  331. if percent < -weekFluctuate {
  332. result = models.AssessmentFormWeekUpYes
  333. }
  334. }
  335. // 如果有进行预测,判断是否正确,返回提示语句
  336. if forecast == "" {
  337. return
  338. }
  339. if forecast == result {
  340. right = true
  341. }
  342. forecastMap := map[string]string{models.AssessmentFormWeekUpYes: "提示风险", models.AssessmentFormWeekUpNo: "未提示风险"}
  343. resultMap := map[string]string{models.AssessmentFormWeekUpYes: "风险发生", models.AssessmentFormWeekUpNo: "风险未发生"}
  344. tips = fmt.Sprint(forecastMap[forecast], " ", resultMap[result])
  345. return
  346. }
  347. // CalculateResultStatistic 计算正确率
  348. func CalculateResultStatistic(forms []*models.AssessmentForm, varietyData []*models.AssessmentVarietyData, results []*models.AssessmentFormResultStatisticItem) (resp []*models.AssessmentFormResultStatisticItem, err error) {
  349. if len(forms) == 0 {
  350. return
  351. }
  352. calculateMappingQ := make(map[string]int) // 月度趋势分子Q(即月度涨跌趋势判断正确的次数)
  353. calculateMappingP := make(map[string]int) // 月度趋势分母P(即月度涨跌趋势判断总次数)
  354. calculateMappingT := make(map[string]int) // 周度风险分子T(即周度上下行风险判断正确的次数)
  355. calculateMappingS := make(map[string]int) // 周度风险分母S(即周度上下行风险判断的总次数)
  356. // 品种数据
  357. varietyDateData := make(map[string]*models.AssessmentVarietyData)
  358. for _, v := range varietyData {
  359. k := fmt.Sprintf("%d-%s", v.VarietyId, v.WeekDate.Format(utils.FormatDate))
  360. varietyDateData[k] = v
  361. }
  362. // 获取品种信息
  363. varietyMatch := make(map[int]*models.AssessmentVariety)
  364. {
  365. varietyOb := new(models.AssessmentVariety)
  366. list, e := varietyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
  367. if e != nil {
  368. err = fmt.Errorf("获取品种信息失败, %v", e)
  369. return
  370. }
  371. for _, v := range list {
  372. varietyMatch[v.AssessmentVarietyId] = v
  373. }
  374. }
  375. // 对填报单的[研究员ID-品种ID]进行QPTS的计数
  376. for _, v := range forms {
  377. vat := varietyMatch[v.VarietyId]
  378. if vat == nil {
  379. utils.FileLog.Info(fmt.Sprintf("CalculateResultStatistic, 品种不存在: %d", v.VarietyId))
  380. continue
  381. }
  382. key := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
  383. // 找出填报单品种对应的基准日期数据、N+1周的最高最低价格、N+4周的价格
  384. var (
  385. baseDataPrice *float64
  386. nextMonthPrice *float64
  387. nextWeekHighPrice *float64
  388. nextWeekLowPrice *float64
  389. )
  390. kb := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.Format(utils.FormatDate))
  391. if varietyDateData[kb] != nil {
  392. baseDataPrice = &varietyDateData[kb].CloseValue
  393. }
  394. kw := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.AddDate(0, 0, 7).Format(utils.FormatDate))
  395. if varietyDateData[kw] != nil {
  396. nextWeekHighPrice = &varietyDateData[kw].HighValue
  397. nextWeekLowPrice = &varietyDateData[kw].LowValue
  398. }
  399. km := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.AddDate(0, 0, 28).Format(utils.FormatDate))
  400. if varietyDateData[km] != nil {
  401. nextMonthPrice = &varietyDateData[km].CloseValue
  402. }
  403. // 月度涨跌评价
  404. if baseDataPrice != nil && nextMonthPrice != nil {
  405. _, _, right := calculateMonthlyPriceTrend(*baseDataPrice, *nextMonthPrice, vat.MonthlyFluctuate, v.MonthlyPriceForecast)
  406. calculateMappingP[key] += 1
  407. if right {
  408. calculateMappingQ[key] += 1
  409. }
  410. }
  411. // 周度上下行风险
  412. if baseDataPrice != nil && nextWeekHighPrice != nil {
  413. _, _, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekHighPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, true)
  414. calculateMappingS[key] += 1
  415. if right {
  416. calculateMappingT[key] += 1
  417. }
  418. }
  419. if baseDataPrice != nil && nextWeekLowPrice != nil {
  420. _, _, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekLowPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, false)
  421. calculateMappingS[key] += 1
  422. if right {
  423. calculateMappingT[key] += 1
  424. }
  425. }
  426. }
  427. // 计算正确率,结果取整
  428. for _, v := range results {
  429. k := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
  430. // 月趋势正确率:Q/P
  431. q := calculateMappingQ[k]
  432. p := calculateMappingP[k]
  433. if p > 0 {
  434. v.MonthlyTrendAccuracy = math.Round(float64(q) / float64(p) * 100)
  435. }
  436. // 周度预警正确率:T/S
  437. t := calculateMappingT[k]
  438. s := calculateMappingS[k]
  439. if s > 0 {
  440. v.WeeklyWarningAccuracy = math.Round(float64(t) / float64(s) * 100)
  441. }
  442. // 综合正确率1:(Q+2T)/(P+2S)
  443. a := q + 2*t
  444. b := p + 2*s
  445. if b > 0 {
  446. v.TotalAccuracyA = math.Round(float64(a) / float64(b) * 100)
  447. }
  448. // 综合正确率2:Q/2P+T/2S
  449. var c, d float64
  450. if p > 0 {
  451. c = math.Round(float64(q) / float64(2*p) * 100)
  452. }
  453. if s > 0 {
  454. d = math.Round(float64(t) / float64(2*s) * 100)
  455. }
  456. v.TotalAccuracyB = c + d
  457. }
  458. resp = results
  459. return
  460. }