Răsfoiți Sursa

fix:超季节性计算调整:
1、通过插值法进行数据补全每天的数据(包含周末)
2、不计算2月29日的数据(闰年)
3、只计算11月-次年5月的数据

Roc 2 ani în urmă
părinte
comite
7f7d1e9550
2 a modificat fișierele cu 209 adăugiri și 78 ștergeri
  1. 164 78
      models/edb_data_calculate_cjjx.go
  2. 45 0
      utils/calculate.go

+ 164 - 78
models/edb_data_calculate_cjjx.go

@@ -7,6 +7,7 @@ import (
 	"github.com/nosixtools/solarlunar"
 	"github.com/shopspring/decimal"
 	"hongze/hongze_edb_lib/utils"
+	"math"
 	"strconv"
 	"strings"
 	"time"
@@ -251,6 +252,13 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 		dateArr = append(dateArr, v.DataTime)
 		dataMap[v.DataTime] = v
 	}
+
+	// 通过插值法补全所有数据(包含周末)
+	handleDataMap := make(map[string]float64)
+	err = handleDataByLinearRegression(dataList, handleDataMap)
+	if err != nil {
+		return
+	}
 	//获取指标所有数据
 	existDataList := make([]*EdbData, 0)
 	dataTableName := GetEdbDataTableName(source)
@@ -270,10 +278,10 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 	addSql := ` INSERT INTO edb_data_calculate_cjjx(edb_info_id,edb_code,data_time,value,create_time,modify_time,data_timestamp) values `
 	var isAdd bool
 	//日度/周度/季度/月度
-	isCompatibility := false //是否向上下兼容35天
-	if utils.InArrayByStr([]string{"日度", "周度", "季度", "月度"}, fromEdbInfo.Frequency) {
-		isCompatibility = true
-	}
+	//isCompatibility := false //是否向上下兼容35天
+	//if utils.InArrayByStr([]string{"日度", "周度", "季度", "月度"}, fromEdbInfo.Frequency) {
+	//	isCompatibility = true
+	//}
 
 	// 每个年份的日期数据需要平移的天数
 	moveDayMap := make(map[int]int, 0) // 每个年份的春节公历
@@ -282,90 +290,99 @@ func refreshAllCalculateCjjx(to orm.TxOrmer, edbInfoId, source int, fromEdbInfo
 		lastDataDay, _ = time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
 	}
 	for _, av := range dateArr {
-		currentItem := dataMap[av]
-		if currentItem != nil {
-			pastValueList := make([]float64, 0) // 过去几期的数据
-			//当前日期
-			currentDate, tmpErr := time.Parse(utils.FormatDate, av)
-			if tmpErr != nil {
-				err = tmpErr
-				return
-			}
-			pastValueList = append(pastValueList, currentItem.Value)
-
-			for i := 1; i < formulaInt; i++ {
-				//前几年当天公历的日期
-				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
-					}
+		// 如果遇到闰二月,如2.29,去掉该天数据
+		if strings.Contains(av, "02-29") {
+			continue
+		}
+		currentDate, tmpErr := time.Parse(utils.FormatDate, av)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		//超季节性指标计算只计算11月--次年5月,分段计算,与数据区间和N数值有关
+		if currentDate.Month() > 5 && currentDate.Month() < 11 {
+			continue
+		}
 
-					// 移动天数到对应农历 的 公历 日期
-					hisoryPreDate = hisoryPreDate.AddDate(0, 0, moveDay)
-				}
+		currentItem, ok := dataMap[av]
+		// 找不到数据就退出当前循环,进入下一循环
+		if !ok {
+			continue
+		}
 
-				hisoryPreDateStr := hisoryPreDate.Format(utils.FormatDate)
-				if findItem, ok := dataMap[hisoryPreDateStr]; ok { //上一年同期找到
-					pastValueList = append(pastValueList, findItem.Value)
-				} else if isCompatibility { // 如果需要兼容上下35天
-					nextDateDay := hisoryPreDate
-					preDateDay := hisoryPreDate
-					for i := 0; i < 35; i++ {
-						nextDateDayStr := nextDateDay.Format(utils.FormatDate)
-						if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
-							pastValueList = append(pastValueList, findItem.Value)
-							break
-						} else {
-							preDateDayStr := preDateDay.Format(utils.FormatDate)
-							if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
-								pastValueList = append(pastValueList, findItem.Value)
-								break
-							}
-						}
-						nextDateDay = nextDateDay.AddDate(0, 0, 1)
-						preDateDay = preDateDay.AddDate(0, 0, -1)
+		pastValueList := make([]float64, 0) // 过去几期的数据
+		//当前日期
+		pastValueList = append(pastValueList, currentItem.Value)
+
+		for i := 1; i < formulaInt; i++ {
+			//前几年当天公历的日期
+			historyPreDate := currentDate.AddDate(-i, 0, 0)
+			moveDay := 0
+			if calendar == "农历" {
+				if tmpMoveDay, ok := moveDayMap[historyPreDate.Year()]; !ok {
+					moveDay, err = getMoveDay(lastDataDay, historyPreDate)
+					if err != nil {
+						return
 					}
+				} else {
+					moveDay = tmpMoveDay
 				}
-				if av == "2022-11-28" {
-					fmt.Println(moveDay)
-				}
-			}
-			if av == "2022-11-28" {
-				fmt.Println(pastValueList)
-			}
 
-			if len(pastValueList) == formulaInt {
-				delete(removeDataTimeMap, av) //将待删除的日期给移除
+				// 移动天数到对应农历 的 公历 日期
+				historyPreDate = historyPreDate.AddDate(0, 0, moveDay)
+			}
 
-				val := CjjxSub(currentItem.Value, pastValueList)
+			historyPreDateStr := historyPreDate.Format(utils.FormatDate)
+			if tmpValue, ok := handleDataMap[historyPreDateStr]; ok { //上一年同期找到
+				pastValueList = append(pastValueList, tmpValue)
+			}
+			//if findItem, ok := dataMap[hisoryPreDateStr]; ok { //上一年同期找到
+			//	pastValueList = append(pastValueList, findItem.Value)
+			//} else if isCompatibility { // 如果需要兼容上下35天
+			//	nextDateDay := hisoryPreDate
+			//	preDateDay := hisoryPreDate
+			//	for i := 0; i < 35; i++ {
+			//		nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+			//		if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+			//			pastValueList = append(pastValueList, findItem.Value)
+			//			break
+			//		} else {
+			//			preDateDayStr := preDateDay.Format(utils.FormatDate)
+			//			if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+			//				pastValueList = append(pastValueList, findItem.Value)
+			//				break
+			//			}
+			//		}
+			//		nextDateDay = nextDateDay.AddDate(0, 0, 1)
+			//		preDateDay = preDateDay.AddDate(0, 0, -1)
+			//	}
+			//}
+		}
 
-				if existVal, ok := existDataMap[edbCode+av]; !ok {
-					timestamp := currentDate.UnixNano() / 1e6
-					timestampStr := fmt.Sprintf("%d", timestamp)
-					addSql += GetAddSql(edbInfoIdStr, edbCode, av, timestampStr, val)
-					isAdd = true
-				} else {
-					existValDecimal, err := decimal.NewFromString(existVal)
-					existStr := existValDecimal.String()
-					if existStr != val {
-						sql := ` UPDATE %s SET value=?,modify_time=NOW() WHERE edb_info_id=? AND data_time=? `
-						sql = fmt.Sprintf(sql, dataTableName)
-						_, err = to.Raw(sql, val, edbInfoId, av).Exec()
-						if err != nil {
-							return err
-						}
+		if len(pastValueList) == formulaInt {
+			delete(removeDataTimeMap, av) //将待删除的日期给移除
+
+			val := CjjxSub(currentItem.Value, pastValueList)
+
+			if existVal, ok := existDataMap[edbCode+av]; !ok {
+				timestamp := currentDate.UnixNano() / 1e6
+				timestampStr := fmt.Sprintf("%d", timestamp)
+				addSql += GetAddSql(edbInfoIdStr, edbCode, av, timestampStr, val)
+				isAdd = true
+			} else {
+				existValDecimal, err := decimal.NewFromString(existVal)
+				existStr := existValDecimal.String()
+				if existStr != val {
+					sql := ` UPDATE %s SET value=?,modify_time=NOW() WHERE edb_info_id=? AND data_time=? `
+					sql = fmt.Sprintf(sql, dataTableName)
+					_, err = to.Raw(sql, val, edbInfoId, av).Exec()
+					if err != nil {
+						return err
 					}
 				}
 			}
-			existDataMap[edbCode+av] = av
 		}
+		existDataMap[edbCode+av] = av
 	}
 
 	//删除已经不存在的超季节性指标数据(由于该指标当日的数据删除了)
@@ -432,7 +449,7 @@ func getMoveDay(lastDataDay, currentDataDay time.Time) (moveDay int, err error)
 
 // 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。
+// 举例:A指标  2022-10-13值100,2021-10-13值120,2020-10-13值110,设置n=3,则“超季节性”指标计算值为100-(100+120+110)/3=-10。
 func CjjxSub(currValue float64, pastValue []float64) (value string) {
 	num := len(pastValue)
 	if num == 0 {
@@ -454,3 +471,72 @@ func CjjxSub(currValue float64, pastValue []float64) (value string) {
 	valStr := decimal.NewFromFloat(val).RoundCeil(4).String()
 	return valStr
 }
+
+// handleDataByLinearRegression 插值法补充数据(线性方程式)
+func handleDataByLinearRegression(edbInfoDataList []*EdbInfoSearchData, handleDataMap map[string]float64) (err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *EdbInfoSearchData
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 获取两条数据之间相差的天数
+		startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+		currDataTime, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+		betweenDay := betweenHour / 24
+
+		// 如果相差一天,那么过滤
+		if betweenDay <= 1 {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			tmpCoordinate1 := utils.Coordinate{
+				X: 1,
+				Y: startEdbInfoData.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate1)
+			tmpCoordinate2 := utils.Coordinate{
+				X: float64(betweenDay) + 1,
+				Y: v.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate2)
+
+			a, b = utils.GetLinearResult(coordinateData)
+			if math.IsNaN(a) || math.IsNaN(b) {
+				err = errors.New("线性方程公式生成失败")
+				return
+			}
+		}
+
+		// 生成对应的值
+		{
+			for i := 1; i < betweenDay; i++ {
+				tmpDataTime := startDataTime.AddDate(0, 0, i)
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).RoundCeil(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}

+ 45 - 0
utils/calculate.go

@@ -0,0 +1,45 @@
+package utils
+
+// Series is a container for a series of data
+type Series []Coordinate
+
+// Coordinate holds the data in a series
+type Coordinate struct {
+	X, Y float64
+}
+
+// GetLinearResult 生成线性方程式
+func GetLinearResult(s []Coordinate) (gradient, intercept float64) {
+	if len(s) <= 1 {
+		return
+	}
+
+	// Placeholder for the math to be done
+	var sum [5]float64
+
+	// Loop over data keeping index in place
+	i := 0
+	for ; i < len(s); i++ {
+		sum[0] += s[i].X
+		sum[1] += s[i].Y
+		sum[2] += s[i].X * s[i].X
+		sum[3] += s[i].X * s[i].Y
+		sum[4] += s[i].Y * s[i].Y
+	}
+
+	// Find gradient and intercept
+	f := float64(i)
+	gradient = (f*sum[3] - sum[0]*sum[1]) / (f*sum[2] - sum[0]*sum[0])
+	intercept = (sum[1] / f) - (gradient * sum[0] / f)
+
+	//fmt.Println("gradient:", gradient, ";intercept:", intercept)
+	// Create the new regression series
+	//for j := 0; j < len(s); j++ {
+	//	regressions = append(regressions, Coordinate{
+	//		X: s[j].X,
+	//		Y: s[j].X*gradient + intercept,
+	//	})
+	//}
+
+	return
+}