123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- package services
- import (
- "eta/eta_api/models"
- "eta/eta_api/utils"
- "fmt"
- "math"
- "sync"
- "time"
- )
- // 填报单单号锁
- var (
- formCodeMutex sync.Mutex
- formCodeUnique sync.Map
- formCodeLastDate string
- )
- // GenerateAssessmentFormCode 生成单号
- func GenerateAssessmentFormCode() (formCode string, err error) {
- formCodeMutex.Lock()
- defer formCodeMutex.Unlock()
- currentDate := time.Now().Format("20060102")
- maxAttempts := 1000
- baseNumber := 1
- // 检查日期变化,清空map
- if formCodeLastDate != "" && formCodeLastDate != currentDate {
- formCodeUnique = sync.Map{}
- }
- formCodeLastDate = currentDate
- // 获取单号最大ID
- formOb := new(models.AssessmentForm)
- maxForm, e := formOb.GetItemByCondition(``, make([]interface{}, 0), fmt.Sprintf("%s DESC", formOb.Cols().PrimaryId))
- if e != nil && !utils.IsErrNoRow(e) {
- err = fmt.Errorf("获取最大单号失败, %v", e)
- return
- }
- if maxForm != nil && maxForm.AssessmentFormId > 0 {
- baseNumber = maxForm.AssessmentFormId + 1
- }
- // 尝试生成自增ID
- for i := 0; i < maxAttempts; i++ {
- serialNumber := fmt.Sprintf("%06d", baseNumber+i)
- uniqueCode := fmt.Sprintf("GDKH%s%s", currentDate, serialNumber)
- if _, loaded := formCodeUnique.LoadOrStore(uniqueCode, true); loaded {
- continue
- }
- // 检查单号是否已存在
- cond := fmt.Sprintf(` AND %s = ?`, formOb.Cols().FormCode)
- pars := make([]interface{}, 0)
- pars = append(pars, uniqueCode)
- exists, e := formOb.GetCountByCondition(cond, pars)
- if e != nil {
- formCodeUnique.Delete(uniqueCode)
- err = fmt.Errorf("检查单号是否存在失败, %v", e)
- return
- }
- if exists <= 0 {
- formCode = uniqueCode
- return
- }
- }
- err = fmt.Errorf("生成单号失败, 尝试次数超过%d次", maxAttempts)
- return
- }
- // GetAssessmentWeekAndFriday 获取给定日期的周数,以及所在周的周五
- // 规则:某年的第一个周五所在周为该年的第一周
- // 返回:格式化字符串(如"202504")、周数(如4)和所在周的周五日期
- func GetAssessmentWeekAndFriday(t time.Time) (string, int, time.Time) {
- year := t.Year()
- // 找到该年第一个周五的日期
- firstFriday := findFirstFriday(year)
- // 计算当前日期与该年第一个周五所在周的第一天(周一)的天数差
- daysSinceFirstWeek := t.YearDay() - firstFriday.YearDay() + int(firstFriday.Weekday()) - int(time.Monday)
- weekNum := 1
- if daysSinceFirstWeek > 0 {
- weekNum += daysSinceFirstWeek / 7
- if daysSinceFirstWeek%7 != 0 {
- weekNum++
- }
- } else {
- // 如果当前日期在第一个周五所在周之前,则属于上一年的最后一周
- prevYear := year - 1
- prevFirstFriday := findFirstFriday(prevYear)
- daysInPrevYear := daysInYear(prevYear)
- daysSincePrevFirstWeek := t.YearDay() + (daysInPrevYear - prevFirstFriday.YearDay()) + int(prevFirstFriday.Weekday()) - int(time.Monday)
- weekNum = daysSincePrevFirstWeek / 7
- if daysSincePrevFirstWeek%7 != 0 {
- weekNum++
- }
- year = prevYear
- }
- // 计算当前日期所在周的周五
- currentWeekFriday := findFridayOfWeek(t)
- // 格式化输出
- formatted := fmt.Sprintf("%04d%02d", year, weekNum)
- return formatted, weekNum, currentWeekFriday
- }
- // findFirstFriday 找到某年的第一个周五
- func findFirstFriday(year int) time.Time {
- // 从1月1日开始找
- date := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
- // 找到第一个周五
- for date.Weekday() != time.Friday {
- date = date.AddDate(0, 0, 1)
- }
- return date
- }
- // findFridayOfWeek 找到给定日期所在周的周五
- func findFridayOfWeek(t time.Time) time.Time {
- // 获取当前日期是周几 (0=周日, 1=周一, ..., 6=周六)
- weekday := int(t.Weekday())
- // 计算到周五的天数差 (周五是5)
- daysToFriday := (5 - weekday + 7) % 7
- // 如果当前就是周五,daysToFriday会是0
- if daysToFriday < 0 {
- daysToFriday += 7
- }
- return t.AddDate(0, 0, daysToFriday)
- }
- // daysInYear 计算某年有多少天
- func daysInYear(year int) int {
- first := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC)
- last := time.Date(year, time.December, 31, 0, 0, 0, 0, time.UTC)
- return last.YearDay() - first.YearDay() + 1
- }
- // CheckAssessmentFormButton 校验填报单按钮
- func CheckAssessmentFormButton(item *models.AssessmentForm, sysAdminId int, authResearcherIds []int) (button models.AssessmentFormButton) {
- if item == nil || sysAdminId <= 0 {
- return
- }
- // 已提交状态
- if item.Status == models.AssessmentFormStatusSubmitted {
- if sysAdminId == item.ResearcherAdminId || utils.InArrayByInt(authResearcherIds, item.ResearcherId) {
- button.ViewButton = true
- }
- // 本周结束前可撤销(TODO:这里象屿会有一个外部条件:当周周报未提交,暂不处理视作true)
- if sysAdminId == item.ResearcherAdminId && !item.WeekEnd.IsZero() && time.Now().Local().Before(item.WeekEnd) {
- button.CancelButton = true
- }
- }
- // 待提交状态(仅自己有操作按钮)
- if item.Status == models.AssessmentFormStatusDraft && sysAdminId == item.ResearcherAdminId {
- button.EditButton = true
- button.RemoveButton = true
- // 本周结束前可提交
- if !item.WeekEnd.IsZero() && time.Now().Local().Before(item.WeekEnd) {
- button.SubmitButton = true
- }
- }
- return
- }
- // GetVarietyPriceAndForecastComment 获取品种价格详情及观点评价
- func GetVarietyPriceAndForecastComment(forms []*models.AssessmentForm) (varietyPrice []*models.AssessmentFormVarietyPrice, forecastComment []*models.AssessmentFormForecastComment, err error) {
- if len(forms) == 0 {
- return
- }
- baseDate := forms[0].BaseDate
- var varietyIds []int
- for _, v := range forms {
- varietyIds = append(varietyIds, v.VarietyId)
- }
- if len(varietyIds) == 0 || baseDate.IsZero() {
- return
- }
- // 获取品种信息
- varietyOb := new(models.AssessmentVariety)
- varietyMatch := make(map[int]*models.AssessmentVariety)
- {
- cond := fmt.Sprintf(` AND %s IN (?)`, varietyOb.Cols().PrimaryId)
- pars := make([]interface{}, 0)
- pars = append(pars, varietyIds)
- list, e := varietyOb.GetItemsByCondition(cond, pars, []string{}, "")
- if e != nil {
- err = fmt.Errorf("获取品种信息失败, %v", e)
- return
- }
- for _, v := range list {
- varietyMatch[v.AssessmentVarietyId] = v
- }
- }
- varietyLatestData := make(map[int]*models.AssessmentVarietyData)
- varietyBaseDateData := make(map[int]*models.AssessmentVarietyData)
- varietyNextWeekData := make(map[int]*models.AssessmentVarietyData)
- varietyNextMonthData := make(map[int]*models.AssessmentVarietyData)
- // 获取最新日期和数据
- dataOb := new(models.AssessmentVarietyData)
- latestData, e := dataOb.GetVarietyMaxDateData(varietyIds)
- if e != nil {
- err = fmt.Errorf("获取品种最新数据失败, %v", e)
- return
- }
- for _, v := range latestData {
- varietyLatestData[v.VarietyId] = v
- }
- // 获取基准日期N至N+4周的数据
- var dateArr []string
- nextWeek := baseDate.AddDate(0, 0, 7)
- nextMonth := baseDate.AddDate(0, 0, 28)
- dateArr = append(dateArr, baseDate.Format(utils.FormatDate), nextWeek.Format(utils.FormatDate), nextMonth.Format(utils.FormatDate))
- {
- cond := fmt.Sprintf(` AND %s IN (?) AND %s IN (?)`, dataOb.Cols().VarietyId, dataOb.Cols().WeekDate)
- pars := make([]interface{}, 0)
- pars = append(pars, varietyIds, dateArr)
- list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, "")
- if e != nil {
- err = fmt.Errorf("获取基准日至N+4周数据失败, %v", e)
- return
- }
- for _, v := range list {
- if varietyMatch[v.VarietyId] == nil {
- continue
- }
- if baseDate.Equal(v.WeekDate) {
- varietyBaseDateData[v.VarietyId] = v
- continue
- }
- if baseDate.AddDate(0, 0, 7).Equal(v.WeekDate) {
- varietyNextWeekData[v.VarietyId] = v
- continue
- }
- if baseDate.AddDate(0, 0, 28).Equal(v.WeekDate) {
- varietyNextMonthData[v.VarietyId] = v
- continue
- }
- }
- }
- varietyPrice = make([]*models.AssessmentFormVarietyPrice, 0)
- forecastComment = make([]*models.AssessmentFormForecastComment, 0)
- for _, v := range forms {
- vat := varietyMatch[v.VarietyId]
- if vat == nil {
- utils.FileLog.Info(fmt.Sprintf("GetVarietyPriceAndForecastComment, 品种不存在: %d", v.VarietyId))
- continue
- }
- // 品种价格评价
- vp := new(models.AssessmentFormVarietyPrice)
- vp.VarietyId = v.VarietyId
- vp.VarietyCode = v.VarietyCode
- vp.VarietyName = v.VarietyName
- if varietyLatestData[v.VarietyId] != nil {
- vp.EndDate = varietyLatestData[v.VarietyId].WeekDate.Format(utils.FormatDate)
- vp.LatestValue = fmt.Sprint(varietyLatestData[v.VarietyId].CloseValue)
- }
- var (
- baseDataPrice *float64
- nextMonthPrice *float64
- nextWeekHighPrice *float64
- nextWeekLowPrice *float64
- )
- if varietyBaseDateData[v.VarietyId] != nil {
- baseDataPrice = &varietyBaseDateData[v.VarietyId].CloseValue
- vp.BaseDatePrice = fmt.Sprint(varietyBaseDateData[v.VarietyId].CloseValue)
- }
- if varietyNextWeekData[v.VarietyId] != nil {
- nextWeekHighPrice = &varietyNextWeekData[v.VarietyId].HighValue
- nextWeekLowPrice = &varietyNextWeekData[v.VarietyId].LowValue
- vp.NextWeekPrice = fmt.Sprint(varietyNextWeekData[v.VarietyId].CloseValue)
- }
- if varietyNextMonthData[v.VarietyId] != nil {
- nextMonthPrice = &varietyNextMonthData[v.VarietyId].CloseValue
- vp.NextMonthPrice = fmt.Sprint(varietyNextMonthData[v.VarietyId].CloseValue)
- }
- varietyPrice = append(varietyPrice, vp)
- // 观点评价
- fc := new(models.AssessmentFormForecastComment)
- fc.VarietyId = v.VarietyId
- fc.VarietyCode = v.VarietyCode
- fc.VarietyName = v.VarietyName
- fc.WeekTime = v.WeekTime
- if !v.SubmitTime.IsZero() {
- fc.SubmitTime = v.SubmitTime.Format(utils.FormatDateUnSpace)
- }
- // 月度涨跌评价
- if baseDataPrice != nil && nextMonthPrice != nil {
- _, tips, right := calculateMonthlyPriceTrend(*baseDataPrice, *nextMonthPrice, vat.MonthlyFluctuate, v.MonthlyPriceForecast)
- fc.MonthlyPriceComment = tips
- fc.MonthlyPriceForecastRight = right
- }
- // 周度上下行风险
- if baseDataPrice != nil && nextWeekHighPrice != nil {
- _, tips, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekHighPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, true)
- fc.WeeklyUpComment = tips
- fc.WeeklyUpForecastRight = right
- }
- if baseDataPrice != nil && nextWeekLowPrice != nil {
- _, tips, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekLowPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, false)
- fc.WeeklyDownComment = tips
- fc.WeeklyDownForecastRight = right
- }
- forecastComment = append(forecastComment, fc)
- }
- return
- }
- // calculateMonthly 判断月度涨跌趋势
- func calculateMonthlyPriceTrend(basePrice, monthPrice, monthlyFluctuate float64, forecast string) (result, tips string, right bool) {
- if basePrice <= 0 || monthPrice <= 0 || monthlyFluctuate <= 0 {
- return
- }
- // 计算月度价格变化比例:月度价格/当周价格-1
- percent := (monthPrice/basePrice - 1) * 100
- // 判断价格趋势
- switch {
- case percent > monthlyFluctuate:
- result = models.AssessmentFormMonthlyPriceUp
- case percent < -monthlyFluctuate:
- result = models.AssessmentFormMonthlyPriceDown
- default:
- result = models.AssessmentFormMonthlyPriceShake
- }
- // 如果有进行预测,判断是否正确,返回提示语句
- if forecast == "" {
- return
- }
- tips = fmt.Sprintf("判断 %s,实际 %s", forecast, result)
- if forecast == result {
- right = true
- }
- return
- }
- // calculateWeekUpDownTrend 判断周度上下行风险
- func calculateWeekUpDownTrend(basePrice, weekPrice, weekFluctuate float64, forecast string, calculateUp bool) (result, tips string, right bool) {
- if basePrice <= 0 || weekPrice <= 0 || weekFluctuate <= 0 {
- return
- }
- percent := (weekPrice/basePrice - 1) * 100
- // 上下行
- result = models.AssessmentFormWeekUpNo
- if calculateUp {
- if percent > weekFluctuate {
- result = models.AssessmentFormWeekUpYes
- }
- } else {
- if percent < -weekFluctuate {
- result = models.AssessmentFormWeekUpYes
- }
- }
- // 如果有进行预测,判断是否正确,返回提示语句
- if forecast == "" {
- return
- }
- if forecast == result {
- right = true
- }
- forecastMap := map[string]string{models.AssessmentFormWeekUpYes: "提示风险", models.AssessmentFormWeekUpNo: "未提示风险"}
- resultMap := map[string]string{models.AssessmentFormWeekUpYes: "风险发生", models.AssessmentFormWeekUpNo: "风险未发生"}
- tips = fmt.Sprint(forecastMap[forecast], " ", resultMap[result])
- return
- }
- // CalculateResultStatistic 计算正确率
- func CalculateResultStatistic(forms []*models.AssessmentForm, varietyData []*models.AssessmentVarietyData, results []*models.AssessmentFormResultStatisticItem) (resp []*models.AssessmentFormResultStatisticItem, err error) {
- if len(forms) == 0 {
- return
- }
- calculateMappingQ := make(map[string]int) // 月度趋势分子Q(即月度涨跌趋势判断正确的次数)
- calculateMappingP := make(map[string]int) // 月度趋势分母P(即月度涨跌趋势判断总次数)
- calculateMappingT := make(map[string]int) // 周度风险分子T(即周度上下行风险判断正确的次数)
- calculateMappingS := make(map[string]int) // 周度风险分母S(即周度上下行风险判断的总次数)
- // 品种数据
- varietyDateData := make(map[string]*models.AssessmentVarietyData)
- for _, v := range varietyData {
- k := fmt.Sprintf("%d-%s", v.VarietyId, v.WeekDate.Format(utils.FormatDate))
- varietyDateData[k] = v
- }
- // 获取品种信息
- varietyMatch := make(map[int]*models.AssessmentVariety)
- {
- varietyOb := new(models.AssessmentVariety)
- list, e := varietyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
- if e != nil {
- err = fmt.Errorf("获取品种信息失败, %v", e)
- return
- }
- for _, v := range list {
- varietyMatch[v.AssessmentVarietyId] = v
- }
- }
- // 对填报单的[研究员ID-品种ID]进行QPTS的计数
- for _, v := range forms {
- vat := varietyMatch[v.VarietyId]
- if vat == nil {
- utils.FileLog.Info(fmt.Sprintf("CalculateResultStatistic, 品种不存在: %d", v.VarietyId))
- continue
- }
- key := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
- // 找出填报单品种对应的基准日期数据、N+1周的最高最低价格、N+4周的价格
- var (
- baseDataPrice *float64
- nextMonthPrice *float64
- nextWeekHighPrice *float64
- nextWeekLowPrice *float64
- )
- kb := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.Format(utils.FormatDate))
- if varietyDateData[kb] != nil {
- baseDataPrice = &varietyDateData[kb].CloseValue
- }
- kw := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.AddDate(0, 0, 7).Format(utils.FormatDate))
- if varietyDateData[kw] != nil {
- nextWeekHighPrice = &varietyDateData[kw].HighValue
- nextWeekLowPrice = &varietyDateData[kw].LowValue
- }
- km := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.AddDate(0, 0, 28).Format(utils.FormatDate))
- if varietyDateData[km] != nil {
- nextMonthPrice = &varietyDateData[km].CloseValue
- }
- // 月度涨跌评价
- if baseDataPrice != nil && nextMonthPrice != nil {
- _, _, right := calculateMonthlyPriceTrend(*baseDataPrice, *nextMonthPrice, vat.MonthlyFluctuate, v.MonthlyPriceForecast)
- calculateMappingP[key] += 1
- if right {
- calculateMappingQ[key] += 1
- }
- }
- // 周度上下行风险
- if baseDataPrice != nil && nextWeekHighPrice != nil {
- _, _, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekHighPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, true)
- calculateMappingS[key] += 1
- if right {
- calculateMappingT[key] += 1
- }
- }
- if baseDataPrice != nil && nextWeekLowPrice != nil {
- _, _, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekLowPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, false)
- calculateMappingS[key] += 1
- if right {
- calculateMappingT[key] += 1
- }
- }
- }
- // 计算正确率,结果取整
- for _, v := range results {
- k := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
- // 月趋势正确率:Q/P
- q := calculateMappingQ[k]
- p := calculateMappingP[k]
- if p > 0 {
- v.MonthlyTrendAccuracy = math.Round(float64(q) / float64(p) * 100)
- }
- // 周度预警正确率:T/S
- t := calculateMappingT[k]
- s := calculateMappingS[k]
- if s > 0 {
- v.WeeklyWarningAccuracy = math.Round(float64(t) / float64(s) * 100)
- }
- // 综合正确率1:(Q+2T)/(P+2S)
- a := q + 2*t
- b := p + 2*s
- if b > 0 {
- v.TotalAccuracyA = math.Round(float64(a) / float64(b) * 100)
- }
- // 综合正确率2:Q/2P+T/2S
- var c, d float64
- if p > 0 {
- c = math.Round(float64(q) / float64(2*p) * 100)
- }
- if s > 0 {
- d = math.Round(float64(t) / float64(2*s) * 100)
- }
- v.TotalAccuracyB = c + d
- }
- resp = results
- return
- }
|