|
@@ -1,6 +1,7 @@
|
|
|
package data
|
|
|
|
|
|
import (
|
|
|
+ "encoding/json"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
"github.com/nosixtools/solarlunar"
|
|
@@ -139,24 +140,21 @@ func GetChartPredictEdbInfoDataListByRuleTb(edbInfoId int, tbValue float64, dayL
|
|
|
// @params a float64 去年同期值
|
|
|
// @params b float64 固定同比增速
|
|
|
func TbzDivMul(a, b float64) (result float64) {
|
|
|
- if b != 0 {
|
|
|
- // 去年同期值
|
|
|
- af := decimal.NewFromFloat(a)
|
|
|
+ // 去年同期值
|
|
|
+ af := decimal.NewFromFloat(a)
|
|
|
|
|
|
- // 同比增速
|
|
|
- bf := decimal.NewFromFloat(b)
|
|
|
+ // 同比增速
|
|
|
+ bf := decimal.NewFromFloat(b)
|
|
|
|
|
|
- // 默认1
|
|
|
- cf := decimal.NewFromFloat(1)
|
|
|
+ // 默认1
|
|
|
+ cf := decimal.NewFromFloat(1)
|
|
|
|
|
|
- // 总增速
|
|
|
- val := bf.Add(cf)
|
|
|
+ // 总增速
|
|
|
+ val := bf.Add(cf)
|
|
|
|
|
|
- // 计算
|
|
|
- result, _ = val.Mul(af).RoundCeil(4).Float64()
|
|
|
- } else {
|
|
|
- result = 0
|
|
|
- }
|
|
|
+ // 计算
|
|
|
+ result, _ = val.Mul(af).RoundCeil(4).Float64()
|
|
|
+
|
|
|
return
|
|
|
}
|
|
|
|
|
@@ -1490,3 +1488,325 @@ func handleNhccData(dataList []*data_manage.EdbDataList, moveDay int) (newDataLi
|
|
|
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+// GetChartPredictEdbInfoDataListByRuleNAnnualAverage 根据 N年均值 规则获取预测数据
|
|
|
+// ETA预测规则:N年均值:过去N年同期均值。过去N年可以连续或者不连续,指标数据均用线性插值补全为日度数据后计算;
|
|
|
+func GetChartPredictEdbInfoDataListByRuleNAnnualAverage(edbInfoId int, configValue string, dayList []time.Time, realPredictEdbInfoData, predictEdbInfoData []*data_manage.EdbDataList, existMap map[string]float64) (newPredictEdbInfoData []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
|
|
|
+ // 获取配置的年份列表
|
|
|
+ yearList, _, err := getYearListBySeasonConf(configValue)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ allDataList := make([]*data_manage.EdbDataList, 0)
|
|
|
+ allDataList = append(allDataList, realPredictEdbInfoData...)
|
|
|
+ allDataList = append(allDataList, predictEdbInfoData...)
|
|
|
+ newPredictEdbInfoData = predictEdbInfoData
|
|
|
+
|
|
|
+ // 插值法数据处理
|
|
|
+ handleDataMap := make(map[string]float64)
|
|
|
+ err = handleDataByLinearRegression(allDataList, handleDataMap)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ index := len(allDataList)
|
|
|
+ //获取后面的预测数据
|
|
|
+ predictEdbInfoData = make([]*data_manage.EdbDataList, 0)
|
|
|
+ for k, currentDate := range dayList {
|
|
|
+ // 如果遇到闰二月,如2.29,去掉该天数据
|
|
|
+ if strings.Contains(currentDate.Format(utils.FormatDate), "02-29") {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ tmpK := len(allDataList) - 1 //上1期数据的下标
|
|
|
+ lastDayData := allDataList[tmpK] // 上1期的数据
|
|
|
+
|
|
|
+ tmpHistoryVal := decimal.NewFromFloat(0) //往期的差值总和
|
|
|
+ tmpHistoryValNum := 0 // 往期差值计算的数量
|
|
|
+ for _, year := range yearList {
|
|
|
+ //前几年当日的日期
|
|
|
+ tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, 0)
|
|
|
+ if val, ok := handleDataMap[tmpHistoryCurrentDate.Format(utils.FormatDate)]; ok {
|
|
|
+ tmpHistoryVal = tmpHistoryVal.Add(decimal.NewFromFloat(val))
|
|
|
+ tmpHistoryValNum++
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //计算的差值与选择的年份数量不一致,那么当前日期不计算
|
|
|
+ if tmpHistoryValNum != len(yearList) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ val, _ := tmpHistoryVal.Div(decimal.NewFromInt(int64(tmpHistoryValNum))).RoundCeil(4).Float64()
|
|
|
+
|
|
|
+ currentDateStr := currentDate.Format(utils.FormatDate)
|
|
|
+ tmpData := &data_manage.EdbDataList{
|
|
|
+ EdbDataId: edbInfoId + 100000 + index + k,
|
|
|
+ EdbInfoId: edbInfoId,
|
|
|
+ DataTime: currentDateStr,
|
|
|
+ Value: val,
|
|
|
+ DataTimestamp: currentDate.UnixNano() / 1e6,
|
|
|
+ }
|
|
|
+ newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
|
|
|
+ allDataList = append(allDataList, tmpData)
|
|
|
+ existMap[currentDateStr] = val
|
|
|
+
|
|
|
+ // 继续使用插值法补充新预测日期的数据之间的值
|
|
|
+ err = handleDataByLinearRegression([]*data_manage.EdbDataList{
|
|
|
+ lastDayData, tmpData,
|
|
|
+ }, handleDataMap)
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最大最小值
|
|
|
+ if val < minValue {
|
|
|
+ minValue = val
|
|
|
+ }
|
|
|
+ if val > maxValue {
|
|
|
+ maxValue = val
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// AnnualValueInversionConf 年度值倒推规则
|
|
|
+type AnnualValueInversionConf struct {
|
|
|
+ Value float64 `description:"年度值"`
|
|
|
+ Type int `description:"分配方式,1:均值法;2:同比法"`
|
|
|
+ Year int `description:"同比年份"`
|
|
|
+}
|
|
|
+
|
|
|
+// GetChartPredictEdbInfoDataListByRuleAnnualValueInversion 根据 年度值倒推 规则获取预测数据
|
|
|
+// ETA预测规则:年度值倒推:设定年度值,余额=年度值-年初至今累计值(算法参考累计值),进行余额分配,均值法分配时保证每期数值相等(日度/周度:剩余期数=剩余自然日历天数/今年指标最新日期自然日历天数*今年至今指标数据期数;旬度/月度/季度/半年度:剩余期数=全年期数(36\12\4\2)-今年至今自然日历期数),同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速)
|
|
|
+// 举例:
|
|
|
+// 指标A 日度 最新日期 2023-05-19 年初至今累计值100
|
|
|
+// 设置年度值1000
|
|
|
+// 则余额=1000-100=900
|
|
|
+// 均值法分配:剩余期数=226/139*120=195.11
|
|
|
+// 今年之后的每一期预测值=900/195.11=4.6128
|
|
|
+// 同比法分配:同比增速=900/同比年份5.19的余额
|
|
|
+// 预测值=同比年份5-20的值*(1+同比增速)
|
|
|
+func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, configValue string, dayList []time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*data_manage.EdbDataList, existMap map[string]float64) (newPredictEdbInfoData []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
|
|
|
+ if frequency == "年度" {
|
|
|
+ err = errors.New("当前指标频度是年度,不允许配置年度值倒推")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取配置
|
|
|
+ var annualValueInversionConf AnnualValueInversionConf
|
|
|
+ err = json.Unmarshal([]byte(configValue), &annualValueInversionConf)
|
|
|
+ if err != nil {
|
|
|
+ err = errors.New("年度值倒推配置信息异常:" + err.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ allDataList := make([]*data_manage.EdbDataList, 0)
|
|
|
+ allDataList = append(allDataList, realPredictEdbInfoData...)
|
|
|
+ allDataList = append(allDataList, predictEdbInfoData...)
|
|
|
+ newPredictEdbInfoData = predictEdbInfoData
|
|
|
+ index := len(allDataList)
|
|
|
+
|
|
|
+ // 配置的年度值
|
|
|
+ yearValueConfig := annualValueInversionConf.Value
|
|
|
+
|
|
|
+ // 当前年的日期
|
|
|
+ currDayTime := dayList[0]
|
|
|
+ lastDayTime := dayList[len(dayList)-1]
|
|
|
+ if currDayTime.Year() != lastDayTime.Year() {
|
|
|
+ err = errors.New("年度值倒推不支持跨年预测")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 均值法
|
|
|
+ if annualValueInversionConf.Type == 1 {
|
|
|
+ // 当前年的期数
|
|
|
+ currYearN := 0
|
|
|
+ var currYearVal float64
|
|
|
+ for _, v := range allDataList {
|
|
|
+ currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
|
|
|
+ if tmpErr != nil {
|
|
|
+ err = tmpErr
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // 只是计算今年的
|
|
|
+ if currTime.Year() != currDayTime.Year() {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ currYearN++
|
|
|
+ currYearVal = currYearVal + v.Value
|
|
|
+ }
|
|
|
+
|
|
|
+ var averageVal float64
|
|
|
+ switch frequency {
|
|
|
+ case "半年度":
|
|
|
+ averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(2 - currYearN))).Float64()
|
|
|
+ case "季度":
|
|
|
+ averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(4 - currYearN))).Float64()
|
|
|
+ case "月度":
|
|
|
+ averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(12 - currYearN))).Float64()
|
|
|
+ case "旬度":
|
|
|
+ averageVal, _ = (decimal.NewFromFloat(yearValueConfig).Sub(decimal.NewFromFloat(currYearVal))).Div(decimal.NewFromInt(int64(36 - currYearN))).Float64()
|
|
|
+ case "周度", "日度":
|
|
|
+ //剩余期数=剩余自然日历天数/今年指标最新日期自然日历天数*今年至今指标数据期数
|
|
|
+
|
|
|
+ // 当前年的第一天
|
|
|
+ yearFirstDay := time.Date(currDayTime.Year(), 1, 1, 0, 0, 0, 0, time.Local)
|
|
|
+ subDay := utils.GetTimeSubDay(yearFirstDay, currDayTime) + 1
|
|
|
+
|
|
|
+ // 当前年的最后一天
|
|
|
+ yearLastDay := time.Date(currDayTime.Year(), 12, 31, 0, 0, 0, 0, time.Local)
|
|
|
+ subDay2 := utils.GetTimeSubDay(yearFirstDay, yearLastDay) + 1
|
|
|
+
|
|
|
+ surplusN := decimal.NewFromInt(int64(subDay2 - subDay)).Div(decimal.NewFromInt(int64(subDay))).Mul(decimal.NewFromInt(int64(currYearN)))
|
|
|
+ averageVal, _ = (decimal.NewFromFloat(annualValueInversionConf.Value).Sub(decimal.NewFromFloat(currYearVal))).Div(surplusN).Round(4).Float64()
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保留四位小数
|
|
|
+ averageVal, _ = decimal.NewFromFloat(averageVal).Round(4).Float64()
|
|
|
+
|
|
|
+ for k, currentDate := range dayList {
|
|
|
+ currentDateStr := currentDate.Format(utils.FormatDate)
|
|
|
+ tmpData := &data_manage.EdbDataList{
|
|
|
+ EdbDataId: edbInfoId + 100000 + index + k,
|
|
|
+ EdbInfoId: edbInfoId,
|
|
|
+ DataTime: currentDateStr,
|
|
|
+ Value: averageVal,
|
|
|
+ DataTimestamp: currentDate.UnixNano() / 1e6,
|
|
|
+ }
|
|
|
+ newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
|
|
|
+ allDataList = append(allDataList, tmpData)
|
|
|
+ existMap[currentDateStr] = averageVal
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最大最小值
|
|
|
+ if averageVal < minValue {
|
|
|
+ minValue = averageVal
|
|
|
+ }
|
|
|
+ if averageVal > maxValue {
|
|
|
+ maxValue = averageVal
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 同比法分配
|
|
|
+ // 同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速);
|
|
|
+ // 同比法分配:同比增速=900/同比年份5.19的余额
|
|
|
+
|
|
|
+ // 每年截止到当前日期的累计值
|
|
|
+ dateTotalMap := make(map[time.Time]float64)
|
|
|
+ // 每年的累计值(计算使用)
|
|
|
+ yearTotalMap := make(map[int]float64)
|
|
|
+ for _, v := range allDataList {
|
|
|
+ currTime, tmpErr := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
|
|
|
+ if tmpErr != nil {
|
|
|
+ err = tmpErr
|
|
|
+ return
|
|
|
+ }
|
|
|
+ yearVal := yearTotalMap[currTime.Year()]
|
|
|
+ yearVal = yearVal + v.Value
|
|
|
+ yearTotalMap[currTime.Year()] = yearVal
|
|
|
+ dateTotalMap[currTime] = yearVal
|
|
|
+ }
|
|
|
+
|
|
|
+ //(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速);
|
|
|
+ for k, currentDate := range dayList {
|
|
|
+ currYearBalance := yearValueConfig - yearTotalMap[currentDate.Year()] // 当年的余额
|
|
|
+ //同比年份相应日期
|
|
|
+ lastYear := annualValueInversionConf.Year + (currentDate.Year() - currDayTime.Year())
|
|
|
+
|
|
|
+ var lastDateTime time.Time
|
|
|
+
|
|
|
+ switch frequency {
|
|
|
+ case "半年度", "季度":
|
|
|
+ lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
|
|
|
+ case "月度":
|
|
|
+ lastDateTime = time.Date(lastYear, currentDate.Month()+1, 1, 0, 0, 0, 0, currentDate.Location()).AddDate(0, 0, -1)
|
|
|
+ case "旬度":
|
|
|
+ if currentDate.Day() == 10 || currentDate.Day() == 20 {
|
|
|
+ lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
|
|
|
+ } else {
|
|
|
+ lastDateTime = time.Date(lastYear, currentDate.Month()+1, 1, 0, 0, 0, 0, currentDate.Location()).AddDate(0, 0, -1)
|
|
|
+ }
|
|
|
+ case "周度", "日度":
|
|
|
+ lastDateTime = time.Date(lastYear, currentDate.Month(), currentDate.Day(), 0, 0, 0, 0, currentDate.Location())
|
|
|
+ }
|
|
|
+
|
|
|
+ // 同比年份相应日期的累计值
|
|
|
+ var dateTotal float64
|
|
|
+ dateTotal, ok := dateTotalMap[lastDateTime]
|
|
|
+ if !ok { //如果没有找到这个日期,那么就往前面找,一直到找到这个累计值,或者找完这一年
|
|
|
+ yearFirstDayTime := time.Date(lastDateTime.Year(), 1, 1, 0, 0, 0, 0, lastDateTime.Location())
|
|
|
+ for tmpDateTime := lastDateTime.AddDate(0, 0, -1); tmpDateTime.After(yearFirstDayTime) || tmpDateTime.Equal(yearFirstDayTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
|
|
|
+ dateTotal, ok = dateTotalMap[tmpDateTime]
|
|
|
+ if ok {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //同比年份相应日期的余额
|
|
|
+ lastYearDateBalance := yearValueConfig - dateTotal
|
|
|
+
|
|
|
+ // 同比增速=余额/同比年份相应日期的余额
|
|
|
+ tbVal := decimal.NewFromFloat(currYearBalance).Div(decimal.NewFromFloat(lastYearDateBalance))
|
|
|
+
|
|
|
+ // 获取同比年份同期值,获取失败的话,就不处理
|
|
|
+ if lastDateVal, ok := existMap[lastDateTime.Format(utils.FormatDate)]; ok {
|
|
|
+ //预测值 = 同比年份同期值*同比增速
|
|
|
+ tmpVal, _ := decimal.NewFromFloat(lastDateVal).Mul(tbVal).Round(4).Float64()
|
|
|
+ currentDateStr := currentDate.Format(utils.FormatDate)
|
|
|
+ tmpData := &data_manage.EdbDataList{
|
|
|
+ EdbDataId: edbInfoId + 100000 + index + k,
|
|
|
+ EdbInfoId: edbInfoId,
|
|
|
+ DataTime: currentDateStr,
|
|
|
+ Value: tmpVal,
|
|
|
+ DataTimestamp: currentDate.UnixNano() / 1e6,
|
|
|
+ }
|
|
|
+ newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
|
|
|
+ allDataList = append(allDataList, tmpData)
|
|
|
+ existMap[currentDateStr] = tmpVal
|
|
|
+
|
|
|
+ yearVal := yearTotalMap[currentDate.Year()]
|
|
|
+ yearVal = yearVal + tmpVal
|
|
|
+ yearTotalMap[currentDate.Year()] = yearVal
|
|
|
+ dateTotalMap[currentDate] = yearVal
|
|
|
+
|
|
|
+ // 最大最小值
|
|
|
+ if tmpVal < minValue {
|
|
|
+ minValue = tmpVal
|
|
|
+ }
|
|
|
+ if tmpVal > maxValue {
|
|
|
+ maxValue = tmpVal
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// getYearListBySeasonConf 根据配置获取年份列表
|
|
|
+func getYearListBySeasonConf(configValue string) (yearList []int, seasonConf SeasonConf, err error) {
|
|
|
+ tmpErr := json.Unmarshal([]byte(configValue), &seasonConf)
|
|
|
+ if tmpErr != nil {
|
|
|
+ err = errors.New("年份配置信息异常:" + tmpErr.Error())
|
|
|
+ return
|
|
|
+ }
|
|
|
+ //选择方式,1:连续N年;2:指定年份
|
|
|
+ if seasonConf.YearType == 1 {
|
|
|
+ if seasonConf.NValue < 1 {
|
|
|
+ err = errors.New("连续N年不允许小于1")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ currYear := time.Now().Year()
|
|
|
+ for i := 0; i < seasonConf.NValue; i++ {
|
|
|
+ yearList = append(yearList, currYear-i-1)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ yearList = seasonConf.YearList
|
|
|
+ }
|
|
|
+
|
|
|
+ return
|
|
|
+}
|