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 }