Browse Source

Merge branch 'chart/12.1'

Roc 2 years ago
parent
commit
0fe116fee4

+ 1 - 1
controllers/base_from_calculate.go

@@ -1409,7 +1409,7 @@ func (this *CalculateController) Refresh() {
 		}
 		formulaInt, _ := strconv.Atoi(edbInfo.CalculateFormula)
 		startDate = `` //只要填写日期,就会出现问题,还是把日期给去掉吧
-		err = models.RefreshAllCalculateCjjx(edbInfoId, edbInfo.Source, fromEdbInfo, calculateCjjx.EdbCode, startDate, "", formulaInt)
+		err = models.RefreshAllCalculateCjjx(edbInfoId, edbInfo.Source, fromEdbInfo, calculateCjjx.EdbCode, startDate, "", edbInfo.Calendar, formulaInt)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			errMsg = "RefreshAllCalculateCjjx Err:" + err.Error()
 			break

+ 1 - 0
go.mod

@@ -8,6 +8,7 @@ require (
 	github.com/beego/beego/v2 v2.0.2
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-sql-driver/mysql v1.6.0
+	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.32
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1

+ 2 - 0
go.sum

@@ -330,6 +330,8 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
 github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19 h1:LhWT2dBuNkYexwRSsPpYh67e0ikmH1ebBDaVkGHoMts=
+github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19/go.mod h1:LjhyrWzOLJ9l1azMoNr9iCvfNrHEREqvJHzSLQcD0/o=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
 github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=

+ 2 - 0
models/base_from_calculate.go

@@ -599,6 +599,7 @@ type EdbInfoCalculateBatchSaveReq struct {
 	}
 	MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency string `description:"移动频度:天/周/月/季/年"`
+	Calendar      string `description:"公历/农历"`
 }
 
 // EdbInfoCalculateBatchEditReq 编辑计算指标的请求参数
@@ -618,6 +619,7 @@ type EdbInfoCalculateBatchEditReq struct {
 		FromTag   string `description:"指标对应标签"`
 		MoveValue int    `description:"移动的值"`
 	}
+	Calendar string `description:"公历/农历" orm:"default(公历)"`
 }
 
 // CheckFormula2 校验公式是否正常(比如说除法的分母不能为0之类的,实际上就是用预设的字段数据做一次计算)

+ 1 - 1
models/base_predict_from_calculate.go

@@ -347,7 +347,7 @@ func refreshAllPredictCalculate(to orm.TxOrmer, edbInfoIdList []*EdbInfo, edbInf
 		formulaStr = strings.ToUpper(formulaStr)
 		formulaFormStr := ReplaceFormula(edbInfoIdList, sv, formulaMap, formulaStr, edbInfoIdBytes)
 		if formulaFormStr != "" {
-			utils.FileLog.Info(fmt.Sprintf("formulaFormStr:%s", formulaFormStr))
+			//utils.FileLog.Info(fmt.Sprintf("formulaFormStr:%s", formulaFormStr))
 			expression := formula.NewExpression(formulaFormStr)
 			calResult, tmpErr := expression.Evaluate()
 			if tmpErr != nil {

+ 103 - 14
models/edb_data_calculate_cjjx.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
+	"github.com/nosixtools/solarlunar"
 	"github.com/shopspring/decimal"
 	"hongze/hongze_edb_lib/utils"
 	"strconv"
@@ -43,6 +44,7 @@ func AddCalculateCjjx(req *EdbInfoCalculateBatchSaveReq, fromEdbInfo *EdbInfo, e
 		edbInfo.ModifyTime = time.Now()
 		edbInfo.UniqueCode = uniqueCode
 		edbInfo.CalculateFormula = req.Formula
+		edbInfo.Calendar = req.Calendar
 		edbInfo.EdbType = 2
 		newEdbInfoId, tmpErr := to.Insert(edbInfo)
 		if tmpErr != nil {
@@ -88,7 +90,7 @@ func AddCalculateCjjx(req *EdbInfoCalculateBatchSaveReq, fromEdbInfo *EdbInfo, e
 	}
 
 	//计算数据
-	err = refreshAllCalculateCjjx(to, edbInfo.EdbInfoId, edbInfo.Source, fromEdbInfo, edbInfo.EdbCode, "", "", formulaInt)
+	err = refreshAllCalculateCjjx(to, edbInfo.EdbInfoId, edbInfo.Source, fromEdbInfo, edbInfo.EdbCode, "", "", edbInfo.Calendar, formulaInt)
 
 	return
 }
@@ -110,6 +112,7 @@ func EditCalculateCjjx(req *EdbInfoCalculateBatchEditReq, edbInfo, fromEdbInfo *
 	}()
 
 	oldCalculateFormula := edbInfo.CalculateFormula //原先的n值
+	oldCalendar := edbInfo.Calendar                 //原先的公历、农历
 	edbInfo, err = GetEdbInfoById(req.EdbInfoId)
 	if err != nil {
 		return
@@ -122,8 +125,9 @@ func EditCalculateCjjx(req *EdbInfoCalculateBatchEditReq, edbInfo, fromEdbInfo *
 	edbInfo.Unit = req.Unit
 	edbInfo.ClassifyId = req.ClassifyId
 	edbInfo.CalculateFormula = req.Formula
+	edbInfo.Calendar = req.Calendar
 	edbInfo.ModifyTime = time.Now()
-	_, err = to.Update(edbInfo, "EdbName", "EdbNameSource", "Frequency", "Unit", "ClassifyId", "CalculateFormula", "ModifyTime")
+	_, err = to.Update(edbInfo, "EdbName", "EdbNameSource", "Frequency", "Unit", "ClassifyId", "CalculateFormula", "Calendar", "ModifyTime")
 	if err != nil {
 		return
 	}
@@ -139,7 +143,7 @@ func EditCalculateCjjx(req *EdbInfoCalculateBatchEditReq, edbInfo, fromEdbInfo *
 		err = errors.New("判断指标是否改变失败,Err:" + err.Error())
 		return
 	}
-	if count > 0 && oldCalculateFormula == req.Formula { // 指标未被替换,同时N值未修改,无需重新计算
+	if count > 0 && oldCalculateFormula == req.Formula && oldCalendar == req.Calendar { // 指标未被替换,同时N值未修改,同时公历/农历未变更,无需重新计算
 		return
 	}
 
@@ -187,13 +191,13 @@ func EditCalculateCjjx(req *EdbInfoCalculateBatchEditReq, edbInfo, fromEdbInfo *
 	}
 
 	//计算数据
-	err = refreshAllCalculateCjjx(to, edbInfo.EdbInfoId, edbInfo.Source, fromEdbInfo, edbInfo.EdbCode, "", "", formulaInt)
+	err = refreshAllCalculateCjjx(to, edbInfo.EdbInfoId, edbInfo.Source, fromEdbInfo, edbInfo.EdbCode, "", "", edbInfo.Calendar, formulaInt)
 
 	return
 }
 
 // RefreshAllCalculateCjjx 刷新全部超季节性数据
-func RefreshAllCalculateCjjx(edbInfoId, source int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate string, formulaInt int) (err error) {
+func RefreshAllCalculateCjjx(edbInfoId, source int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate, calendar string, formulaInt int) (err error) {
 	o := orm.NewOrm()
 	to, err := o.Begin()
 	if err != nil {
@@ -209,13 +213,13 @@ func RefreshAllCalculateCjjx(edbInfoId, source int, fromEdbInfo *EdbInfo, edbCod
 	}()
 
 	// 重新计算
-	err = refreshAllCalculateCjjx(to, edbInfoId, source, fromEdbInfo, edbCode, startDate, endDate, formulaInt)
+	err = refreshAllCalculateCjjx(to, edbInfoId, source, fromEdbInfo, edbCode, startDate, endDate, calendar, formulaInt)
 
 	return
 }
 
 // refreshAllCalculateCjjx 刷新全部超季节性数据
-func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate string, formulaInt int) (err error) {
+func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo *EdbInfo, edbCode, startDate, endDate, calendar string, formulaInt int) (err error) {
 	if err != nil {
 		return
 	}
@@ -257,8 +261,10 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 		return err
 	}
 	existDataMap := make(map[string]string)
+	removeDataTimeMap := make(map[string]int) //需要移除的日期数据
 	for _, v := range existDataList {
 		existDataMap[edbCode+v.DataTime] = v.Value
+		removeDataTimeMap[v.DataTime] = 1
 	}
 
 	addSql := ` INSERT INTO edb_data_calculate_cjjx(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
@@ -269,6 +275,12 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 		isCompatibility = true
 	}
 
+	// 每个年份的日期数据需要平移的天数
+	moveDayMap := make(map[int]int, 0) // 每个年份的春节公历
+	var lastDataDay time.Time
+	if len(dataList) > 0 {
+		lastDataDay, _ = time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+	}
 	for _, av := range dateArr {
 		currentItem := dataMap[av]
 		if currentItem != nil {
@@ -280,15 +292,31 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 				return
 			}
 			pastValueList = append(pastValueList, currentItem.Value)
+
 			for i := 1; i < formulaInt; i++ {
-				//前几年的日期
-				preDate := currentDate.AddDate(-i, 0, 0)
-				preDateStr := preDate.Format(utils.FormatDate)
-				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+				//前几年当天公历的日期
+				hisoryPreDate := currentDate.AddDate(-i, 0, 0)
+				moveDay := 0
+				if calendar == "农历" {
+					if tmpMoveDay, ok := moveDayMap[hisoryPreDate.Year()]; !ok {
+						moveDay, err = getMoveDay(lastDataDay, hisoryPreDate)
+						if err != nil {
+							return
+						}
+					} else {
+						moveDay = tmpMoveDay
+					}
+
+					// 移动天数到对应农历 的 公历 日期
+					hisoryPreDate = hisoryPreDate.AddDate(0, 0, moveDay)
+				}
+
+				hisoryPreDateStr := hisoryPreDate.Format(utils.FormatDate)
+				if findItem, ok := dataMap[hisoryPreDateStr]; ok { //上一年同期找到
 					pastValueList = append(pastValueList, findItem.Value)
 				} else if isCompatibility { // 如果需要兼容上下35天
-					nextDateDay := preDate
-					preDateDay := preDate
+					nextDateDay := hisoryPreDate
+					preDateDay := hisoryPreDate
 					for i := 0; i < 35; i++ {
 						nextDateDayStr := nextDateDay.Format(utils.FormatDate)
 						if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
@@ -305,9 +333,17 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 						preDateDay = preDateDay.AddDate(0, 0, -1)
 					}
 				}
+				if av == "2022-11-28" {
+					fmt.Println(moveDay)
+				}
+			}
+			if av == "2022-11-28" {
+				fmt.Println(pastValueList)
 			}
 
 			if len(pastValueList) == formulaInt {
+				delete(removeDataTimeMap, av) //将待删除的日期给移除
+
 				val := CjjxSub(currentItem.Value, pastValueList)
 
 				if existVal, ok := existDataMap[edbCode+av]; !ok {
@@ -331,6 +367,26 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 			existDataMap[edbCode+av] = av
 		}
 	}
+
+	//删除已经不存在的超季节性指标数据(由于该指标当日的数据删除了)
+	{
+		removeDateList := make([]string, 0)
+		for dateTime := range removeDataTimeMap {
+			removeDateList = append(removeDateList, dateTime)
+		}
+		removeNum := len(removeDateList)
+		if removeNum > 0 {
+			//如果拼接指标变更了,那么需要删除所有的指标数据
+			tableName := GetEdbDataTableName(source)
+			sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (`+utils.GetOrmInReplace(removeNum)+`) `, tableName)
+			_, err = to.Raw(sql, edbInfoId, removeDateList).Exec()
+			if err != nil {
+				err = fmt.Errorf("删除不存在的超季节性指标数据失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+
 	if isAdd {
 		addSql = strings.TrimRight(addSql, ",")
 		_, err = to.Raw(addSql).Exec()
@@ -341,6 +397,39 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 	return
 }
 
+// getMoveDay 获取两个日期的平移天数
+func getMoveDay(lastDataDay, currentDataDay time.Time) (moveDay int, err error) {
+	if lastDataDay.Month() >= 11 { //最新数据的日期如果大于等于11月份,那么用的是下一年的春节
+		lastDataDay = lastDataDay.AddDate(1, 0, 0)
+	}
+
+	currentYear := lastDataDay.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
+	}
+
+	//指定年的春节农历
+	tmpYearCjnl := fmt.Sprintf("%d-01-01", currentDataDay.Year())
+	//指定年的春节公历
+	tmpYearCjgl := solarlunar.LunarToSolar(tmpYearCjnl, false)
+
+	tmpYearCjglTime, tmpErr := time.ParseInLocation(utils.FormatDate, tmpYearCjgl, time.Local)
+	if tmpErr != nil {
+		err = errors.New(fmt.Sprintf("%d公历日期转换失败:%s", currentDataDay.Year(), tmpErr.Error()))
+		return
+	}
+
+	// 将两个日期同步到同一年,然后计算两个日期相差的天数
+	tmpCurrentYearCjglTime := currentYearCjglTime.AddDate(currentDataDay.Year()-currentYear, 0, 0)
+	moveDay = utils.GetTimeSubDay(tmpYearCjglTime, tmpCurrentYearCjglTime)
+
+	return
+}
+
 // CjjxSub 计算超季节性值
 // 计算公式=现值-过去n年(包括今年)均值,n为取数个数,需大于等于1;
 //举例:A指标  2022-10-13值100,2021-10-13值120,2020-10-13值110,设置n=3,则“超季节性”指标计算值为100-(100+120+110)/3=-10。
@@ -352,7 +441,7 @@ func CjjxSub(currValue float64, pastValue []float64) (value string) {
 	numDecimal := decimal.NewFromInt(int64(num))
 
 	af := decimal.NewFromFloat(currValue)
-	fmt.Println(af)
+	//fmt.Println(af)
 
 	bf := decimal.NewFromFloat(pastValue[0])
 

+ 1 - 1
models/edb_data_calculate_tcz.go

@@ -283,7 +283,7 @@ func refreshAllCalculateTcz(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo *
 						}
 					}
 				}
-				utils.FileLog.Info("同期找到:" + av + ";" + preDateStr)
+				//utils.FileLog.Info("同期找到:" + av + ";" + preDateStr)
 				continue
 			} else {
 				if fromEdbInfo.Frequency == "月度" { //向上和向下,各找一个月

+ 43 - 0
models/edb_info.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
@@ -43,6 +44,7 @@ type EdbInfo struct {
 	LatestDate  string  `description:"数据最新日期"`
 	LatestValue float64 `description:"数据最新值"`
 	ChartImage  string  `description:"图表图片"`
+	Calendar    string  `description:"公历/农历"`
 }
 
 // AddEdbInfo 添加指标
@@ -589,6 +591,47 @@ func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []*PredictEdbCo
 			}
 			finalValue, _ := tmpValDecimal.Float64()
 			predictEdbInfoData, tmpMinValue, tmpMaxValue = GetChartPredictEdbInfoDataListByRuleFinalValueHc(predictEdbConf.PredictEdbInfoId, finalValue, startDate, dataEndTime, frequency, realPredictEdbInfoData, predictEdbInfoData, existMap)
+		case 11: //11:根据 季节性 规则获取预测数据
+			var seasonConf SeasonConf
+			tmpErr := json.Unmarshal([]byte(predictEdbConf.Value), &seasonConf)
+			if tmpErr != nil {
+				err = errors.New("季节性配置信息异常:" + tmpErr.Error())
+				return
+			}
+			calendar := "公历"
+			if seasonConf.Calendar == "农历" {
+				calendar = "农历"
+			}
+			yearList := make([]int, 0)
+			//选择方式,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
+			}
+			predictEdbInfoData, tmpMinValue, tmpMaxValue, err = GetChartPredictEdbInfoDataListByRuleSeason(predictEdbConf.PredictEdbInfoId, yearList, calendar, startDate, dataEndTime, frequency, realPredictEdbInfoData, predictEdbInfoData, existMap)
+			if err != nil {
+				return
+			}
+		case 12: //12:根据 移动平均同比 规则获取预测数据
+			var moveAverageConf MoveAverageConf
+			tmpErr := json.Unmarshal([]byte(predictEdbConf.Value), &moveAverageConf)
+			if tmpErr != nil {
+				err = errors.New("季节性配置信息异常:" + tmpErr.Error())
+				return
+			}
+			predictEdbInfoData, tmpMinValue, tmpMaxValue, err = GetChartPredictEdbInfoDataListByRuleMoveAverageTb(predictEdbConf.PredictEdbInfoId, moveAverageConf.NValue, moveAverageConf.Year, startDate, dataEndTime, frequency, realPredictEdbInfoData, predictEdbInfoData, existMap)
+			if err != nil {
+				return
+			}
 		}
 		//startDate = dataEndTime.AddDate(0, 0, 1)
 		if startDate.Before(dataEndTime) {

+ 327 - 0
models/predict_edb_info_rule.go

@@ -2,6 +2,8 @@ package models
 
 import (
 	"errors"
+	"fmt"
+	"github.com/nosixtools/solarlunar"
 	"github.com/shopspring/decimal"
 	"hongze/hongze_edb_lib/utils"
 	"math"
@@ -709,3 +711,328 @@ func GetChartPredictEdbInfoDataListByRuleFinalValueHc(edbInfoId int, finalValue
 	}
 	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 []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) {
+	allDataList := make([]*EdbInfoSearchData, 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([]*EdbInfoSearchData, 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 := &EdbInfoSearchData{
+			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 []*EdbInfoSearchData, existMap map[string]float64) (newPredictEdbInfoData []*EdbInfoSearchData, minValue, maxValue float64, err error) {
+	allDataList := make([]*EdbInfoSearchData, 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 := &EdbInfoSearchData{
+			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
+}