ソースを参照

Merge branch 'feature/eta1.2.0_edb_calculate' into debug

# Conflicts:
#	models/base_predict_from_calculate.go
xyxie 1 年間 前
コミット
9421ed4a08

+ 72 - 0
controllers/base_from_predict.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_index_lib/logic"
 	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/services"
 	"eta/eta_index_lib/utils"
 	"fmt"
 	"strconv"
@@ -183,3 +184,74 @@ func (this *PredictController) Refresh() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// CalculateByNinePreview
+// @Title 动态环差演算
+// @Description 指标运算演算
+// @Param request body models.RuleConfig true "type json string"
+// @Success Ret=200 返回指标id
+// @router /calculate_by_nine/preview [post]
+func (this *PredictController) CalculateByNinePreview() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req models.RuleConfig
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	var errMsg string
+	// 获取计算参数
+	formula, edbInfoList, edbInfoIdBytes, err, errMsg := services.GetCalculateByRuleByNineParams(req)
+	if err != nil {
+		br.Msg = "计算失败"
+		if errMsg != "" {
+			br.Msg = errMsg
+		}
+		br.Msg = err.Error()
+		br.IsSendEmail = false
+		return
+	}
+	// 获取计算数据
+	dataList, err := services.CalculateByRuleByNine(formula, edbInfoList, edbInfoIdBytes, req.EmptyType, req.MaxEmptyType)
+	if err != nil {
+		br.Msg = "数据计算失败"
+		br.ErrMsg = "数据计算失败:Err:" + err.Error()
+		br.IsSendEmail = false
+		return
+	}
+
+	latestDate := time.Now()
+	for _, v := range edbInfoList {
+		tmpLatestDate, err := time.ParseInLocation(utils.FormatDate, v.LatestDate, time.Local)
+		if err != nil {
+			continue
+		}
+		if tmpLatestDate.Before(latestDate) {
+			latestDate = tmpLatestDate
+		}
+	}
+
+	newDataList := make([]*models.EdbDataList, 0)
+	lenData := len(dataList)
+	if lenData > 0 {
+		for i := lenData - 1; i >= 0; i-- {
+			newDataList = append(newDataList, dataList[i])
+		}
+	}
+	resp := models.PredictRuleCalculateByNineResp{
+		LatestDate: latestDate.Format(utils.FormatDate),
+		DataList:   newDataList,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 34 - 23
models/base_from_calculate.go

@@ -7,7 +7,6 @@ import (
 	"github.com/beego/beego/v2/client/orm"
 	"github.com/shopspring/decimal"
 	"github.com/yidane/formula"
-	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -437,7 +436,7 @@ func refreshAllCalculate(to orm.TxOrmer, edbInfoIdArr []*EdbInfo, edbInfoId, sou
 	}
 
 	//数据处理,将日期内不全的数据做补全
-	handleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, edbInfoIdArr, emptyType)
+	HandleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, edbInfoIdArr, emptyType)
 
 	formulaDateSlice, formulaDateMap, err := utils.HandleFormulaJson(formulaStr, maxStartDate.Format(utils.FormatDate))
 	if err != nil {
@@ -597,13 +596,13 @@ func refreshAllCalculate(to orm.TxOrmer, edbInfoIdArr []*EdbInfo, edbInfoId, sou
 func ReplaceFormula(edbInfoIdArr []*EdbInfo, valArr, valArrMax map[int]float64, formulaMap map[string]string, formulaStr string, edbInfoIdBytes []string, maxDealFlag bool) string {
 	// todo 先处理max和min函数的特殊情况
 	//if strings.Contains(formulaStr, "MAX") || strings.Contains(formulaStr, "MIN") {
-	if maxDealFlag {
-		formulaStr = GetMaxMinEdbInfo(formulaStr)
-	}
 	funMap := GetFormulaMap()
 	for k, v := range funMap {
 		formulaStr = strings.Replace(formulaStr, k, v, -1)
 	}
+	if maxDealFlag {
+		formulaStr = GetMaxMinEdbInfo(formulaStr)
+	}
 	replaceCount := 0
 	for dk, dv := range edbInfoIdArr {
 		var isReplace bool
@@ -658,7 +657,7 @@ func GetFormulaMap() map[string]string {
 	funMap["FLOOR"] = "[@^]"
 	funMap["MOD"] = "[@&]"
 	funMap["POW"] = "[@*]"
-	funMap["ROUND"] = "[@(]"
+	funMap["ROUND"] = "[@`]"
 	funMap["LN"] = "[@-]"
 	funMap["EXP"] = "[@+]"
 	return funMap
@@ -747,7 +746,7 @@ func CheckFormula2(edbInfoArr []*EdbInfo, formulaMap map[string]string, formulaS
 }
 
 // 处理整个数据
-func handleDateSaveDataMap(dateList []string, maxStartDate, minLatestDate time.Time, realSaveDataMap, saveDataMap map[string]map[int]float64, edbInfoIdArr []*EdbInfo, emptyType int) {
+func HandleDateSaveDataMap(dateList []string, maxStartDate, minLatestDate time.Time, realSaveDataMap, saveDataMap map[string]map[int]float64, edbInfoIdArr []*EdbInfo, emptyType int) {
 	var startDate, endDate string
 	var startDateT, endDateT time.Time
 	if emptyType == 2 || emptyType == 3 {
@@ -769,7 +768,7 @@ func handleDateSaveDataMap(dateList []string, maxStartDate, minLatestDate time.T
 		if dateTime.Before(maxStartDate) {
 			continue
 		}
-		// 如果当前日期晚于数据的最小结束日期,那么不处理,直接返回了
+		// todo 规则是否生效如果当前日期晚于数据的最小结束日期,那么不处理,直接返回了
 		if dateTime.After(minLatestDate) {
 			return
 		}
@@ -830,6 +829,7 @@ func handleDateDataMap(realSaveDataMap, saveDataMap map[string]map[int]float64,
 			}
 			if findDataMap, hasFindDataMap := realSaveDataMap[nextDateDayStr]; hasFindDataMap { // 下一个日期有数据
 				if val, hasFindItem := findDataMap[edbInfoId]; hasFindItem {
+					fmt.Println(fmt.Sprintf("date:%s, 无值,取%s的值%.4f", date, nextDateDayStr, val))
 					saveDataMap[date][edbInfoId] = val
 					return
 				}
@@ -843,6 +843,7 @@ func handleDateDataMap(realSaveDataMap, saveDataMap map[string]map[int]float64,
 			}
 			if findDataMap, hasFindDataMap := realSaveDataMap[preDateDayStr]; hasFindDataMap { // 下一个日期有数据
 				if val, hasFindItem := findDataMap[edbInfoId]; hasFindItem {
+					fmt.Println(fmt.Sprintf("date:%s, 无值,取%s的值%.4f", date, preDateDayStr, val))
 					saveDataMap[date][edbInfoId] = val
 					return
 				}
@@ -942,20 +943,30 @@ func handleDateDataMapZero(saveDataMap map[string]map[int]float64, date string,
 }
 
 func GetMaxMinEdbInfo(formula string) string {
-	//formula := "A+min(A,B,max(A,C))"
-	// todo 无法处理max里嵌套max或者min的情况
-	// 使用正则表达式匹配MAX和MIN函数及其参数
-	regex := regexp.MustCompile(`(?i)(MAX|MIN)\((.*?)\)`)
-	matches := regex.FindAllStringSubmatch(formula, -1)
-	// 遍历匹配结果,输出MAX和MIN函数及其参数
-	for _, match := range matches {
-		if len(match) == 3 {
-			parameter := strings.ToLower(match[0]) // 参数
-			formula = strings.ReplaceAll(formula, match[0], parameter)
-			fmt.Printf("formula: %s\n", formula)
-		}
-	}
-	formula = strings.ReplaceAll(formula, "max", "MAX")
-	formula = strings.ReplaceAll(formula, "min", "MIN")
+	flag := 0
+	for k, v := range formula {
+		if v >= 65 && v <= 122 {
+			// 从左边找到第一个左括号的位置,往前截取三个字符,判断是不是max或者min,如果是的话,把该入参改成小写
+			for i := k; i >= 4; i-- {
+				if formula[i:i+1] == "(" && flag >= 1 {
+					t := formula[i-4 : i]
+					if t == "[@@]" || t == "[@!]" {
+						formula = formula[:k] + strings.ToLower(formula[k:k+1]) + formula[k+1:]
+					}
+					break
+				}
+			}
+		} else if formula[k:k+1] == "(" {
+			flag += 1
+		} else if formula[k:k+1] == ")" {
+			flag -= 1
+		}
+	}
 	return formula
 }
+
+// PredictRuleCalculateByNineResp 获取预测指标规则9的绘图数据返回
+type PredictRuleCalculateByNineResp struct {
+	LatestDate string
+	DataList   interface{}
+}

+ 4 - 4
models/base_predict_from_calculate.go

@@ -332,10 +332,9 @@ func refreshAllPredictCalculate(to orm.TxOrmer, edbInfoIdList []*EdbInfo, edbInf
 			}
 		}
 	}
-
-	//todo debug 冲突处理数据处理,将日期内不全的数据做填补
-	//handleDateSaveDataMap(dateList, maxStartDate, minEndDate, realSaveDataMap, saveDataMap, edbInfoIdList)
-	handleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, edbInfoIdList, emptyType)
+	
+	//数据处理,将日期内不全的数据做填补
+	HandleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, edbInfoIdList, emptyType)
 
 	formulaDateSlice, formulaDateMap, err := utils.HandleFormulaJson(formulaStr, maxStartDate.Format(utils.FormatDate))
 	if err != nil {
@@ -404,6 +403,7 @@ func refreshAllPredictCalculate(to orm.TxOrmer, edbInfoIdList []*EdbInfo, edbInf
 		formulaFormStr := ReplaceFormula(edbInfoIdList, sv, svMax, formulaMap, formulaStr, edbInfoIdBytes, maxDealFlag)
 		if formulaFormStr != "" {
 			//utils.FileLog.Info(fmt.Sprintf("formulaFormStr:%s", formulaFormStr))
+			fmt.Println(fmt.Sprintf("date %s:formulaFormStr:%s", sk, formulaFormStr))
 			expression := formula.NewExpression(formulaFormStr)
 			calResult, tmpErr := expression.Evaluate()
 			if tmpErr != nil {

+ 1 - 1
models/predict_edb.go

@@ -109,7 +109,7 @@ func CalculateByRuleBy9(to orm.TxOrmer, rule CalculateRule) (resultDataList []*E
 	}
 
 	// todo 数据处理,将日期内不全的数据做填补
-	handleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, rule.EdbInfoList, rule.EmptyType)
+	HandleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, rule.EdbInfoList, rule.EmptyType)
 
 	// 添加数据
 	addDataList := make([]*PredictEdbRuleData, 0)

+ 9 - 0
routers/commentsRouter.go

@@ -763,6 +763,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PredictController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PredictController"],
+        beego.ControllerComments{
+            Method: "CalculateByNinePreview",
+            Router: `/calculate_by_nine/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PredictController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers:PredictController"],
         beego.ControllerComments{
             Method: "Refresh",

+ 234 - 0
services/base_from_predict.go

@@ -0,0 +1,234 @@
+package services
+
+import (
+	"errors"
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"github.com/yidane/formula"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// GetCalculateByRuleByNineParams 获取预测规则9的计算参数
+func GetCalculateByRuleByNineParams(req models.RuleConfig) (formula string, edbInfoList []*models.EdbInfo, edbInfoIdBytes []string, err error, errMsg string) {
+	formula = req.Value
+	formula = strings.Replace(formula, "(", "(", -1)
+	formula = strings.Replace(formula, ")", ")", -1)
+	formula = strings.Replace(formula, ",", ",", -1)
+	formula = strings.Replace(formula, "。", ".", -1)
+	formula = strings.Replace(formula, "%", "*0.01", -1)
+
+	//检验公式
+	var checkFormulaStr string
+	for _, tmpEdbInfoId := range req.EdbInfoIdArr {
+		checkFormulaStr += tmpEdbInfoId.FromTag + ","
+		edbInfoIdBytes = append(edbInfoIdBytes, tmpEdbInfoId.FromTag)
+	}
+
+	formulaSlice, err := utils.CheckFormulaJson(formula)
+	if err != nil {
+		errMsg = "公式格式错误,请重新填写"
+		err = errors.New(errMsg)
+		return
+	}
+	for _, f := range formulaSlice {
+		formulaMap := utils.CheckFormula(f)
+		for _, v := range formulaMap {
+			if !strings.Contains(checkFormulaStr, v) {
+				errMsg = "公式错误,请重新填写"
+				err = errors.New(errMsg)
+				return
+			}
+		}
+	}
+
+	//关联的指标信息
+	edbInfoList = make([]*models.EdbInfo, 0)
+
+	for _, tmpEdbInfoId := range req.EdbInfoIdArr {
+		fromEdbInfo, tmpErr := models.GetEdbInfoById(tmpEdbInfoId.EdbInfoId)
+		if tmpErr != nil {
+			if tmpErr.Error() == utils.ErrNoRow() {
+				err = errors.New("指标 " + strconv.Itoa(tmpEdbInfoId.EdbInfoId) + " 不存在")
+			} else {
+				err = errors.New("获取指标失败:Err:" + tmpErr.Error())
+			}
+			errMsg = "数据计算失败"
+			return
+		}
+		edbInfoList = append(edbInfoList, fromEdbInfo)
+	}
+
+	for _, v := range formulaSlice {
+		formulaMap := utils.CheckFormula(v)
+		//预先计算,判断公式是否正常
+		ok, _ := models.CheckFormula2(edbInfoList, formulaMap, v, edbInfoIdBytes)
+		if !ok {
+			errMsg = "生成计算指标失败,请使用正确的计算公式"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+
+	return
+}
+
+// CalculateByRuleByNine 动态环差规则计算入库
+func CalculateByRuleByNine(formulaStr string, edbInfoList []*models.EdbInfo, edbInfoIdBytes []string, emptyType, maxEmptyType int) (dataList []*models.EdbDataList, err error) {
+	realSaveDataMap := make(map[string]map[int]float64)
+	saveDataMap := make(map[string]map[int]float64)
+	dateList := make([]string, 0) //日期
+
+	// 最小的结束日期 , 最晚的数据开始日期
+	var minLatestDate, maxStartDate time.Time
+	formulaStr = strings.ToUpper(formulaStr)
+	// 获取关联指标数据
+	for edbInfoIndex, v := range edbInfoList {
+		// todo eta_api和eta_index_lib两个函数的逻辑不一致
+		sourceDataList, tmpErr := models.GetPredictEdbDataListAll(v, 1)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		dataMap := make(map[string]float64)
+		lenData := len(sourceDataList)
+		for _, dv := range sourceDataList {
+			// 实际数据
+			if val, ok := realSaveDataMap[dv.DataTime]; ok {
+				if _, ok1 := val[v.EdbInfoId]; !ok1 {
+					val[v.EdbInfoId] = dv.Value
+				}
+			} else {
+				temp := make(map[int]float64)
+				temp[v.EdbInfoId] = dv.Value
+				realSaveDataMap[dv.DataTime] = temp
+			}
+
+			// saveDataMap 待计算的数据
+			if val, ok := saveDataMap[dv.DataTime]; ok {
+				if _, ok1 := val[v.EdbInfoId]; !ok1 {
+					val[v.EdbInfoId] = dv.Value
+				}
+			} else {
+				temp2 := make(map[int]float64)
+				temp2[v.EdbInfoId] = dv.Value
+				saveDataMap[dv.DataTime] = temp2
+			}
+
+			// 以第一个指标的日期作为基准日期
+			if edbInfoIndex == 0 {
+				dateList = append(dateList, dv.DataTime)
+			}
+
+			if lenData > 0 {
+				tmpLatestDate, _ := time.ParseInLocation(utils.FormatDate, sourceDataList[lenData-1].DataTime, time.Local)
+				if minLatestDate.IsZero() || minLatestDate.After(tmpLatestDate) {
+					minLatestDate = tmpLatestDate
+				}
+
+				tmpStartDate, _ := time.ParseInLocation(utils.FormatDate, sourceDataList[0].DataTime, time.Local)
+				if maxStartDate.IsZero() || maxStartDate.Before(tmpStartDate) {
+					maxStartDate = tmpStartDate
+				}
+			}
+		}
+		item := new(models.CalculateItems)
+		item.EdbInfoId = v.EdbInfoId
+		item.DataMap = dataMap
+	}
+
+	models.HandleDateSaveDataMap(dateList, maxStartDate, minLatestDate, realSaveDataMap, saveDataMap, edbInfoList, emptyType)
+	// 添加数据
+	dataList = make([]*models.EdbDataList, 0)
+
+	// 计算规则
+	formulaDateSlice, formulaDateMap, err := utils.HandleFormulaJson(formulaStr, maxStartDate.Format(utils.FormatDate))
+	if err != nil {
+		return
+	}
+	existDataMap := make(map[string]string)
+	// 判断是否特殊处理max和min函数
+	maxDealFlag := false
+	if emptyType == 4 && maxEmptyType == 2 {
+		maxDealFlag = true
+	}
+	for k, date := range dateList {
+		sv := saveDataMap[date]
+		// 当空值处理类型选择了不计算时,只要有一个指标在某个日期没有值(即空值),则计算指标在该日期没有值
+		if emptyType == 1 {
+			if len(sv) != len(edbInfoList) {
+				continue
+			}
+		}
+		//fmt.Println(sk, sv)
+		// 根据时间范围,选择对应的公式
+		formulaMap := make(map[string]string)
+		formulaStr = ""
+		for _, fv := range formulaDateSlice {
+			if date >= fv {
+				if f, ok := formulaDateMap[fv]; ok {
+					formulaStr = f
+					formulaMap = utils.CheckFormula(formulaStr)
+				}
+				break
+			}
+		}
+		if formulaStr == "" {
+			continue
+		}
+		svMax := make(map[int]float64)
+		if maxDealFlag {
+			// 特殊处理max和min函数,如果原本的值为空,则选择空值参与运算
+			if svMaxData, ok := realSaveDataMap[date]; ok {
+				svMax = svMaxData
+			}
+		}
+
+		//fmt.Println(date, sv)
+
+		formulaFormStr := models.ReplaceFormula(edbInfoList, sv, svMax, formulaMap, formulaStr, edbInfoIdBytes, maxDealFlag)
+		if formulaFormStr == `` {
+			//计算公式异常,那么就移除该指标
+			continue
+		}
+
+		//fmt.Println(fmt.Sprintf("formulaFormStr:%s", formulaFormStr))
+		fmt.Println(fmt.Sprintf("%s:formulaFormStr:%s", date, formulaFormStr))
+		expression := formula.NewExpression(formulaFormStr)
+		calResult, tmpErr := expression.Evaluate()
+		if tmpErr != nil {
+			// 分母为0的报错
+			if strings.Contains(tmpErr.Error(), "divide by zero") {
+				continue
+			}
+			err = errors.New("计算失败:Err:" + tmpErr.Error() + ";formulaStr:" + formulaFormStr)
+			return
+		}
+		calVal, tmpErr := calResult.Float64()
+		if tmpErr != nil {
+			err = errors.New("计算失败:获取计算值失败 Err:" + tmpErr.Error() + ";formulaStr:" + formulaFormStr)
+			fmt.Println(err)
+			return
+		}
+
+		saveValue, _ := decimal.NewFromFloat(calVal).RoundCeil(4).Float64() //utils.SubFloatToString(calVal, 4)
+		dataTime, _ := time.Parse(utils.FormatDate, date)
+		timestamp := dataTime.UnixNano() / 1e6
+
+		if _, existOk := existDataMap[date]; !existOk {
+			tmpPredictEdbRuleData := &models.EdbDataList{
+				EdbDataId:     k,
+				EdbInfoId:     0,
+				DataTime:      date,
+				DataTimestamp: timestamp,
+				Value:         saveValue,
+			}
+			dataList = append(dataList, tmpPredictEdbRuleData)
+		}
+		existDataMap[date] = date
+	}
+	return
+}