|
@@ -2,6 +2,8 @@ package chart
|
|
|
|
|
|
import (
|
|
import (
|
|
"errors"
|
|
"errors"
|
|
|
|
+ "fmt"
|
|
|
|
+ "github.com/nosixtools/solarlunar"
|
|
"github.com/shopspring/decimal"
|
|
"github.com/shopspring/decimal"
|
|
edbDataModel "hongze/hongze_yb/models/tables/edb_data"
|
|
edbDataModel "hongze/hongze_yb/models/tables/edb_data"
|
|
predictEdbRuleDataModel "hongze/hongze_yb/models/tables/predict_edb_rule_data"
|
|
predictEdbRuleDataModel "hongze/hongze_yb/models/tables/predict_edb_rule_data"
|
|
@@ -726,3 +728,328 @@ func GetChartPredictEdbInfoDataListByRuleFinalValueHc(edbInfoId int, finalValue
|
|
}
|
|
}
|
|
return
|
|
return
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+// SeasonConf 季节性规则的配置
|
|
|
|
+type SeasonConf struct {
|
|
|
|
+ Calendar string `description:"公历、农历"`
|
|
|
|
+ YearType int `description:"选择方式,1:连续N年;2:指定年份"`
|
|
|
|
+ NValue int `description:"连续N年"`
|
|
|
|
+ YearList []int `description:"指定年份列表"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// GetChartPredictEdbInfoDataListByRuleSeason 根据 季节性 规则获取预测数据
|
|
|
|
+// ETA预测规则:季节性
|
|
|
|
+//已知选定指标A最近更新日期: 2022-12-6 200
|
|
|
|
+//设置预测截止日期2023-01-06
|
|
|
|
+//1、选择过去N年,N=3
|
|
|
|
+//则过去N年为2021、2020、2019
|
|
|
|
+//指标A日期 实际值 指标A日期
|
|
|
|
+//2019/12/5 150 2019/12/6
|
|
|
|
+//2020/12/5 180 2020/12/6
|
|
|
|
+//2021/12/5 210 2021/12/6
|
|
|
|
+//2019/12/31 200 2020/1/1
|
|
|
|
+//2020/12/31 210 2021/1/1
|
|
|
|
+//2021/12/31 250 2022/1/1
|
|
|
|
+//
|
|
|
|
+//计算12.7预测值,求过去N年环差均值=[(100-150)+(160-180)+(250-210)]/3=-10
|
|
|
|
+//则12.7预测值=12.6值+过去N年环差均值=200-10=190
|
|
|
|
+//以此类推...
|
|
|
|
+//
|
|
|
|
+//计算2023.1.2预测值,求过去N年环差均值=[(300-200)+(220-210)+(260-250)]/3=40
|
|
|
|
+//则2023.1.2预测值=2023.1.1值+过去N年环差均值
|
|
|
|
+func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, yearsList []int, calendar string, startDate, endDate time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*edbDataModel.EdbDataList, existMap map[string]float64) (newPredictEdbInfoData []*edbDataModel.EdbDataList, minValue, maxValue float64, err error) {
|
|
|
|
+ allDataList := make([]*edbDataModel.EdbDataList, 0)
|
|
|
|
+ allDataList = append(allDataList, realPredictEdbInfoData...)
|
|
|
|
+ allDataList = append(allDataList, predictEdbInfoData...)
|
|
|
|
+ newPredictEdbInfoData = predictEdbInfoData
|
|
|
|
+
|
|
|
|
+ // 获取每个年份的日期数据需要平移的天数
|
|
|
|
+ moveDayMap := make(map[int]int, 0) // 每个年份的春节公历
|
|
|
|
+ {
|
|
|
|
+ if calendar == "公历" {
|
|
|
|
+ for _, year := range yearsList {
|
|
|
|
+ moveDayMap[year] = 0 //公历就不平移了
|
|
|
|
+ }
|
|
|
|
+ } else {
|
|
|
|
+ currentDay := time.Now()
|
|
|
|
+ if currentDay.Month() >= 11 { //如果大于等于11月份,那么用的是下一年的春节
|
|
|
|
+ currentDay = currentDay.AddDate(1, 0, 0)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ currentYear := currentDay.Year()
|
|
|
|
+ currentYearCjnl := fmt.Sprintf("%d-01-01", currentYear) //当年的春节农历
|
|
|
|
+ currentYearCjgl := solarlunar.LunarToSolar(currentYearCjnl, false) //当年的春节公历
|
|
|
|
+ currentYearCjglTime, tmpErr := time.ParseInLocation(utils.FormatDate, currentYearCjgl, time.Local)
|
|
|
|
+ if tmpErr != nil {
|
|
|
|
+ err = errors.New("当前春节公历日期转换失败:" + tmpErr.Error())
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 指定的年份
|
|
|
|
+ for _, year := range yearsList {
|
|
|
|
+ tmpYearCjnl := fmt.Sprintf("%d-01-01", year) //指定年的春节农历
|
|
|
|
+ tmpYearCjgl := solarlunar.LunarToSolar(tmpYearCjnl, false) //指定年的春节公历
|
|
|
|
+ //moveDayList = append(moveDayList, 0) //公历就不平移了
|
|
|
|
+
|
|
|
|
+ tmpYearCjglTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpYearCjgl, time.Local)
|
|
|
|
+ if tmpErr != nil {
|
|
|
|
+ err = errors.New(fmt.Sprintf("%d公历日期转换失败:%s", year, tmpErr.Error()))
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ tmpCurrentYearCjglTime := currentYearCjglTime.AddDate(year-currentYear, 0, 0)
|
|
|
|
+ moveDay := utils.GetTimeSubDay(tmpYearCjglTime, tmpCurrentYearCjglTime)
|
|
|
|
+ moveDayMap[year] = moveDay //公历平移
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ index := len(allDataList)
|
|
|
|
+ //获取后面的预测日期
|
|
|
|
+ dayList := getPredictEdbDayList(startDate, endDate, frequency)
|
|
|
|
+
|
|
|
|
+ //获取后面的预测数据
|
|
|
|
+ predictEdbInfoData = make([]*edbDataModel.EdbDataList, 0)
|
|
|
|
+ for k, currentDate := range dayList {
|
|
|
|
+ tmpHistoryVal := decimal.NewFromFloat(0) //往期的差值总和
|
|
|
|
+ tmpHistoryValNum := 0 // 往期差值计算的数量
|
|
|
|
+
|
|
|
|
+ tmpLenAllDataList := len(allDataList)
|
|
|
|
+ tmpK := tmpLenAllDataList - 1 //上1期数据的下标
|
|
|
|
+ lastDayStr := allDataList[tmpK].DataTime
|
|
|
|
+ lastDayVal := allDataList[tmpK].Value
|
|
|
|
+ lastDay, tmpErr := time.ParseInLocation(utils.FormatDate, lastDayStr, time.Local)
|
|
|
|
+ if tmpErr != nil {
|
|
|
|
+ err = errors.New("获取上期日期转换失败:" + tmpErr.Error())
|
|
|
|
+ }
|
|
|
|
+ for _, year := range yearsList {
|
|
|
|
+ moveDay := moveDayMap[year] //需要移动的天数
|
|
|
|
+ var tmpHistoryCurrentVal, tmpHistoryLastVal float64
|
|
|
|
+ var isFindHistoryCurrent, isFindHistoryLast bool //是否找到前几年的数据
|
|
|
|
+
|
|
|
|
+ //前几年当日的日期
|
|
|
|
+ tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, moveDay)
|
|
|
|
+ for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
|
|
|
|
+ tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpHistoryCurrentVal = val
|
|
|
|
+ isFindHistoryCurrent = true
|
|
|
|
+ break
|
|
|
|
+ } else {
|
|
|
|
+ tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpHistoryCurrentVal = val
|
|
|
|
+ isFindHistoryCurrent = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //前几年上一期的日期
|
|
|
|
+ tmpHistoryLastDate := lastDay.AddDate(year-lastDay.Year(), 0, moveDay)
|
|
|
|
+ for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
|
|
|
|
+ tmpDate := tmpHistoryLastDate.AddDate(0, 0, i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpHistoryLastVal = val
|
|
|
|
+ isFindHistoryLast = true
|
|
|
|
+ break
|
|
|
|
+ } else {
|
|
|
|
+ tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpHistoryLastVal = val
|
|
|
|
+ isFindHistoryLast = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果两个日期对应的数据都找到了,那么计算两期的差值
|
|
|
|
+ if isFindHistoryCurrent && isFindHistoryLast {
|
|
|
|
+ af := decimal.NewFromFloat(tmpHistoryCurrentVal)
|
|
|
|
+ bf := decimal.NewFromFloat(tmpHistoryLastVal)
|
|
|
|
+ tmpHistoryVal = tmpHistoryVal.Add(af.Sub(bf))
|
|
|
|
+ tmpHistoryValNum++
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ //计算的差值与选择的年份数量不一致,那么当前日期不计算
|
|
|
|
+ if tmpHistoryValNum != len(yearsList) {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ lastDayValDec := decimal.NewFromFloat(lastDayVal)
|
|
|
|
+ val, _ := tmpHistoryVal.Div(decimal.NewFromInt(int64(tmpHistoryValNum))).Add(lastDayValDec).RoundCeil(4).Float64()
|
|
|
|
+
|
|
|
|
+ currentDateStr := currentDate.Format(utils.FormatDate)
|
|
|
|
+ tmpData := &edbDataModel.EdbDataList{
|
|
|
|
+ EdbDataId: edbInfoId + 10000000000 + index + k,
|
|
|
|
+ EdbInfoId: edbInfoId,
|
|
|
|
+ DataTime: currentDateStr,
|
|
|
|
+ Value: val,
|
|
|
|
+ DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适
|
|
|
|
+ }
|
|
|
|
+ newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
|
|
|
|
+ allDataList = append(allDataList, tmpData)
|
|
|
|
+ existMap[currentDateStr] = val
|
|
|
|
+
|
|
|
|
+ // 最大最小值
|
|
|
|
+ if val < minValue {
|
|
|
|
+ minValue = val
|
|
|
|
+ }
|
|
|
|
+ if val > maxValue {
|
|
|
|
+ maxValue = val
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// MoveAverageConf 移动平均同比规则的配置
|
|
|
|
+type MoveAverageConf struct {
|
|
|
|
+ Year int `description:"指定年份"`
|
|
|
|
+ NValue int `description:"N期的数据"`
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// GetChartPredictEdbInfoDataListByRuleMoveAverageTb 根据 移动平均同比 规则获取预测数据
|
|
|
|
+// ETA预测规则:季节性
|
|
|
|
+//2、选择指定N年,N=3
|
|
|
|
+//指定N年为2012、2015、2018
|
|
|
|
+//指标A日期 实际值 指标A日期 实际值
|
|
|
|
+//2012/12/5 150 2012/12/6 130
|
|
|
|
+//2015/12/5 180 2015/12/6 150
|
|
|
|
+//2018/12/5 210 2018/12/6 260
|
|
|
|
+//2012/12/31 200 2013/1/1 200
|
|
|
|
+//2015/12/31 210 2016/1/1 250
|
|
|
|
+//2018/12/31 250 2019/1/1 270
|
|
|
|
+//计算12.7预测值,求过去N年环差均值=[(130-150)+(150-180)+(290-210)]/3=10
|
|
|
|
+//则12.7预测值=12.6值+过去N年环差均值=200+10=210
|
|
|
|
+//以此类推...
|
|
|
|
+//计算2023.1.2预测值,求过去N年环差均值=[(200-200)+(250-210)+(270-250)]/3=16.67
|
|
|
|
+//则2023.1.2预测值=2023.1.1值+过去N年环差均值
|
|
|
|
+func GetChartPredictEdbInfoDataListByRuleMoveAverageTb(edbInfoId int, nValue, year int, startDate, endDate time.Time, frequency string, realPredictEdbInfoData, predictEdbInfoData []*edbDataModel.EdbDataList, existMap map[string]float64) (newPredictEdbInfoData []*edbDataModel.EdbDataList, minValue, maxValue float64, err error) {
|
|
|
|
+ allDataList := make([]*edbDataModel.EdbDataList, 0)
|
|
|
|
+ allDataList = append(allDataList, realPredictEdbInfoData...)
|
|
|
|
+ allDataList = append(allDataList, predictEdbInfoData...)
|
|
|
|
+ newPredictEdbInfoData = predictEdbInfoData
|
|
|
|
+
|
|
|
|
+ lenAllData := len(allDataList)
|
|
|
|
+ if lenAllData < nValue || lenAllData <= 0 {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if nValue <= 0 {
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ // 分母
|
|
|
|
+ decimalN := decimal.NewFromInt(int64(nValue))
|
|
|
|
+
|
|
|
|
+ //获取后面的预测数据
|
|
|
|
+ dayList := getPredictEdbDayList(startDate, endDate, frequency)
|
|
|
|
+ for k, currentDate := range dayList {
|
|
|
|
+ tmpLenAllDataList := len(allDataList)
|
|
|
|
+ tmpIndex := tmpLenAllDataList - 1 //上1期数据的下标
|
|
|
|
+
|
|
|
|
+ averageDateList := make([]string, 0) //计算平均数的日期
|
|
|
|
+
|
|
|
|
+ // 数据集合中的最后一个数据
|
|
|
|
+ tmpDecimalVal := decimal.NewFromFloat(allDataList[tmpIndex].Value)
|
|
|
|
+ averageDateList = append(averageDateList, allDataList[tmpIndex].DataTime)
|
|
|
|
+ for tmpK := 2; tmpK <= nValue; tmpK++ {
|
|
|
|
+ tmpIndex2 := tmpIndex - tmpK //上N期的值
|
|
|
|
+ tmpDecimalVal2 := decimal.NewFromFloat(allDataList[tmpIndex2].Value)
|
|
|
|
+ tmpDecimalVal = tmpDecimalVal.Add(tmpDecimalVal2)
|
|
|
|
+ averageDateList = append(averageDateList, allDataList[tmpIndex2].DataTime)
|
|
|
|
+ }
|
|
|
|
+ // 最近的N期平均值
|
|
|
|
+ tmpAverageVal := tmpDecimalVal.Div(decimalN)
|
|
|
|
+
|
|
|
|
+ var tmpHistoryCurrentVal float64 // 前几年当日的数据值
|
|
|
|
+ var isFindHistoryCurrent, isFindHistoryLast bool //是否找到前几年的数据
|
|
|
|
+ tmpHistoryDecimalVal := decimal.NewFromFloat(0) //前几年N期数据总值
|
|
|
|
+
|
|
|
|
+ {
|
|
|
|
+ // 前几年N期汇总期数
|
|
|
|
+ tmpHistoryValNum := 0
|
|
|
|
+ {
|
|
|
|
+ //前几年当日的日期
|
|
|
|
+ tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, 0)
|
|
|
|
+ for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
|
|
|
|
+ tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpHistoryCurrentVal = val
|
|
|
|
+ isFindHistoryCurrent = true
|
|
|
|
+ break
|
|
|
|
+ } else {
|
|
|
|
+ tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpHistoryCurrentVal = val
|
|
|
|
+ isFindHistoryCurrent = true
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _, averageDate := range averageDateList {
|
|
|
|
+ lastDay, tmpErr := time.ParseInLocation(utils.FormatDate, averageDate, time.Local)
|
|
|
|
+ if tmpErr != nil {
|
|
|
|
+ err = tmpErr
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ //前几年上一期的日期
|
|
|
|
+ tmpHistoryLastDate := lastDay.AddDate(year-lastDay.Year(), 0, 0)
|
|
|
|
+ for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
|
|
|
|
+ tmpDate := tmpHistoryLastDate.AddDate(0, 0, i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpDecimalVal2 := decimal.NewFromFloat(val)
|
|
|
|
+ tmpHistoryDecimalVal = tmpHistoryDecimalVal.Add(tmpDecimalVal2)
|
|
|
|
+ tmpHistoryValNum++
|
|
|
|
+ break
|
|
|
|
+ } else {
|
|
|
|
+ tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i)
|
|
|
|
+ if val, ok := existMap[tmpDate.Format(utils.FormatDate)]; ok {
|
|
|
|
+ tmpDecimalVal2 := decimal.NewFromFloat(val)
|
|
|
|
+ tmpHistoryDecimalVal = tmpHistoryDecimalVal.Add(tmpDecimalVal2)
|
|
|
|
+ tmpHistoryValNum++
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 汇总期数与配置的N期数量一致
|
|
|
|
+ if tmpHistoryValNum == nValue {
|
|
|
|
+ isFindHistoryLast = true
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 如果没有找到前几年的汇总数据,或者没有找到前几年当日的数据,那么退出当前循环,进入下一循环
|
|
|
|
+ if !isFindHistoryLast || !isFindHistoryCurrent {
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 计算最近N期同比值
|
|
|
|
+ tbVal := tmpAverageVal.Div(tmpHistoryDecimalVal)
|
|
|
|
+
|
|
|
|
+ // 预测值结果 = 同比年份同期值(tmpHistoryCurrentVal的值)* 同比值(tbVal的值)
|
|
|
|
+ val, _ := decimal.NewFromFloat(tmpHistoryCurrentVal).Mul(tbVal).RoundCeil(4).Float64()
|
|
|
|
+
|
|
|
|
+ currentDateStr := currentDate.Format(utils.FormatDate)
|
|
|
|
+ tmpData := &edbDataModel.EdbDataList{
|
|
|
|
+ EdbDataId: edbInfoId + 10000000000 + lenAllData + k,
|
|
|
|
+ EdbInfoId: edbInfoId,
|
|
|
|
+ DataTime: currentDateStr,
|
|
|
|
+ Value: val,
|
|
|
|
+ DataTimestamp: (currentDate.UnixNano() / 1e6) + 1000, //前端需要让加1s,说是2022-09-01 00:00:00 这样的整点不合适
|
|
|
|
+ }
|
|
|
|
+ newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
|
|
|
|
+ allDataList = append(allDataList, tmpData)
|
|
|
|
+ existMap[currentDateStr] = val
|
|
|
|
+
|
|
|
|
+ // 最大最小值
|
|
|
|
+ if val < minValue {
|
|
|
|
+ minValue = val
|
|
|
|
+ }
|
|
|
|
+ if val > maxValue {
|
|
|
|
+ maxValue = val
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return
|
|
|
|
+}
|