Browse Source

feat:新增拟合残差计算规则

Roc 2 years ago
parent
commit
8a2e826c2c

+ 160 - 55
controllers/base_from_calculate.go

@@ -480,17 +480,13 @@ func (this *CalculateController) BatchSave() {
 		return
 	}
 
-	if req.FromEdbInfoId <= 0 {
-		br.Msg = "请选择指标"
-		return
-	}
+	// 基础指标id
+	fromEdbInfoId := req.FromEdbInfoId
 
 	var formulaInt int
-	if req.Source == utils.DATA_SOURCE_CALCULATE_NSZYDPJJS ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_HBZ ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_HCZ ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_TIME_SHIFT ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_CJJX {
+	var nhccDate models.NhccDate // 拟合残差的日期
+	switch req.Source {
+	case utils.DATA_SOURCE_CALCULATE_NSZYDPJJS, utils.DATA_SOURCE_CALCULATE_HBZ, utils.DATA_SOURCE_CALCULATE_HCZ, utils.DATA_SOURCE_CALCULATE_TIME_SHIFT, utils.DATA_SOURCE_CALCULATE_CJJX:
 		if req.Formula == "" {
 			br.Msg = "请填写N值"
 			return
@@ -500,17 +496,51 @@ func (this *CalculateController) BatchSave() {
 			br.Msg = "N值输入错误,请重新输入"
 			return
 		}
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_ZJPJ {
+	case utils.DATA_SOURCE_CALCULATE_ZJPJ:
 		//直接拼接指标
-
 		//校验时间格式
 		_, err = time.ParseInLocation(utils.FormatDate, req.Formula, time.Local)
 		if err != nil {
 			br.Msg = "拼接日期有误,请重新输入"
 			return
 		}
+	case utils.DATA_SOURCE_CALCULATE_NHCC: //拟合残差指标
+		//指标校验
+		if len(req.EdbInfoIdArr) != 2 {
+			br.Msg = "选择的指标异常,请重新选择"
+			return
+		}
+		fromEdbInfoId = req.EdbInfoIdArr[0].EdbInfoId
+
+		//校验时间格式
+		//数据格式:2022-11-01,2022-11-10
+		timeList := strings.Split(req.Formula, ",")
+		if len(timeList) != 2 {
+			br.Msg = "选择时间有误,请重新输入"
+			return
+		}
+		startDate, err := time.ParseInLocation(utils.FormatDate, timeList[0], time.Local)
+		if err != nil {
+			br.Msg = "开始日期有误,请重新输入"
+			return
+		}
+		endDate, err := time.ParseInLocation(utils.FormatDate, timeList[1], time.Local)
+		if err != nil {
+			br.Msg = "结束日期有误,请重新输入"
+			return
+		}
+		if utils.GetTimeSubDay(startDate, endDate) < 2 {
+			br.Msg = "日期间隔不得少于两天"
+			return
+		}
+		nhccDate.StartDate = startDate
+		nhccDate.EndDate = endDate
 	}
 
+	if fromEdbInfoId <= 0 {
+		br.Msg = "请选择指标"
+		return
+	}
 	//加入缓存机制,避免创建同一个名称的指标 start
 	redisKey := fmt.Sprint("edb_lib:edb_info:calculate:batch:save:", req.Source, ":", req.EdbName)
 	isExist := utils.Rc.IsExist(redisKey)
@@ -545,7 +575,7 @@ func (this *CalculateController) BatchSave() {
 		return
 	}
 
-	fromEdbInfo, err := models.GetEdbInfoById(req.FromEdbInfoId)
+	fromEdbInfo, err := models.GetEdbInfoById(fromEdbInfoId)
 	if err != nil {
 		br.Msg = "获取指标信息失败"
 		br.ErrMsg = "获取指标信息失败:Err:" + err.Error()
@@ -564,27 +594,28 @@ func (this *CalculateController) BatchSave() {
 	var sourName string
 	var edbInfoId int
 	var edbInfo *models.EdbInfo
-	if req.Source == utils.DATA_SOURCE_CALCULATE_LJZZY {
+	switch req.Source {
+	case utils.DATA_SOURCE_CALCULATE_LJZZY:
 		sourName = "累计值转月值"
 		if fromEdbInfo.Frequency != "月度" {
 			br.Msg = "请选择月度指标"
 			return
 		}
 		edbInfo, err = models.AddCalculateLjzzy(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_TBZ {
+	case utils.DATA_SOURCE_CALCULATE_TBZ:
 		sourName = "同比值"
 		edbInfo, err = models.AddCalculateTbz(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_TCZ {
+	case utils.DATA_SOURCE_CALCULATE_TCZ:
 		sourName = "同差值"
 		edbInfo, err = models.AddCalculateTcz(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_NSZYDPJJS {
+	case utils.DATA_SOURCE_CALCULATE_NSZYDPJJS:
 		sourName = "N数值移动平均计算"
 		edbInfo, err = models.AddCalculateNszydpjjs(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName, formulaInt)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_HBZ {
+	case utils.DATA_SOURCE_CALCULATE_HBZ:
 		var condition string
 		var pars []interface{}
 		condition += " AND edb_info_id =? "
-		pars = append(pars, req.FromEdbInfoId)
+		pars = append(pars, fromEdbInfoId)
 		condition += " AND value <=0 "
 		checkCount, err := models.GetEdbDataCount(condition, pars, fromEdbInfo.Source)
 		if err != nil && err.Error() != utils.ErrNoRow() {
@@ -599,10 +630,10 @@ func (this *CalculateController) BatchSave() {
 		}
 		sourName = "环比值"
 		edbInfo, err = models.AddCalculateHbz(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName, formulaInt)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_HCZ {
+	case utils.DATA_SOURCE_CALCULATE_HCZ:
 		sourName = "环差值"
 		edbInfo, err = models.AddCalculateHcz(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName, formulaInt)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_BP {
+	case utils.DATA_SOURCE_CALCULATE_BP:
 		//if fromEdbInfo.Frequency == "日度" {
 		//	br.Msg = "日度指标,无法进行变频操作"
 		//	br.ErrMsg = "日度指标,无法进行变频操作:edbcode:" + fromEdbInfo.EdbCode
@@ -610,10 +641,10 @@ func (this *CalculateController) BatchSave() {
 		//}
 		sourName = "变频"
 		edbInfo, err = models.AddCalculateBp(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_TIME_SHIFT { //时间移位
+	case utils.DATA_SOURCE_CALCULATE_TIME_SHIFT:
 		sourName = "时间移位"
 		edbInfo, err = models.AddCalculateTimeShift(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_ZJPJ { //直接拼接
+	case utils.DATA_SOURCE_CALCULATE_ZJPJ:
 		sourName = "直接拼接"
 
 		if len(req.EdbInfoIdArr) != 1 {
@@ -636,7 +667,7 @@ func (this *CalculateController) BatchSave() {
 			return
 		}
 		edbInfo, err = models.AddCalculateZjpj(&req, fromEdbInfo, secondEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_LJZTBPJ { //累计值同比拼接
+	case utils.DATA_SOURCE_CALCULATE_LJZTBPJ:
 		sourName = "累计值同比拼接"
 
 		if fromEdbInfo.Frequency != "月度" {
@@ -674,10 +705,27 @@ func (this *CalculateController) BatchSave() {
 			return
 		}
 		edbInfo, err = models.AddCalculateLjztbpj(&req, fromEdbInfo, tbzEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_CJJX {
+	case utils.DATA_SOURCE_CALCULATE_CJJX:
 		sourName = "超季节性"
 		edbInfo, err = models.AddCalculateCjjx(&req, fromEdbInfo, edbCode, uniqueCode, sysUserId, sysUserName, formulaInt)
-	} else {
+	case utils.DATA_SOURCE_CALCULATE_NHCC:
+		sourName = "拟合残差"
+
+		secondEdbInfoReq := req.EdbInfoIdArr[1]
+		secondEdbInfo, err := models.GetEdbInfoById(secondEdbInfoReq.EdbInfoId)
+		if err != nil {
+			br.Msg = "获取因变量的指标信息失败"
+			br.ErrMsg = "获取因变量的指标信息失败:Err:" + err.Error()
+			return
+		}
+
+		if fromEdbInfo.EdbInfoId == secondEdbInfo.EdbInfoId {
+			br.Msg = "两个指标不允许为同一个"
+			br.ErrMsg = "两个指标不允许为同一个"
+			return
+		}
+		edbInfo, err = models.AddCalculateNhcc(&req, fromEdbInfo, secondEdbInfo, edbCode, uniqueCode, nhccDate, sysUserId, sysUserName)
+	default:
 		br.Msg = "无效计算方式"
 		br.ErrMsg = "无效计算方式,source:" + strconv.Itoa(req.Source)
 		return
@@ -815,28 +863,18 @@ func (this *CalculateController) BatchEdit() {
 		return
 	}
 
-	fromEdbInfo, err := models.GetEdbInfoById(req.FromEdbInfoId)
-	if err != nil {
-		if err.Error() == utils.ErrNoRow() {
-			br.Msg = "指标已被删除,请刷新页面"
-			br.ErrMsg = "指标已被删除,请刷新页面:Err:" + err.Error()
-			return
-		}
-		br.Msg = "获取指标信息失败"
-		br.ErrMsg = "获取指标信息失败:Err:" + err.Error()
-		return
-	}
+	// 基础指标id
+	fromEdbInfoId := req.FromEdbInfoId
 
 	if req.Source <= 0 {
 		req.Source = edbInfo.Source
 	}
 
 	var formulaInt int
-	if req.Source == utils.DATA_SOURCE_CALCULATE_NSZYDPJJS ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_HBZ ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_HCZ ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_TIME_SHIFT ||
-		req.Source == utils.DATA_SOURCE_CALCULATE_CJJX {
+	var nhccDate models.NhccDate // 拟合残差的日期
+	// 初期的参数校验
+	switch req.Source {
+	case utils.DATA_SOURCE_CALCULATE_NSZYDPJJS, utils.DATA_SOURCE_CALCULATE_HBZ, utils.DATA_SOURCE_CALCULATE_HCZ, utils.DATA_SOURCE_CALCULATE_TIME_SHIFT, utils.DATA_SOURCE_CALCULATE_CJJX:
 		if req.Formula == "" {
 			br.Msg = "请填写N值"
 			return
@@ -846,7 +884,7 @@ func (this *CalculateController) BatchEdit() {
 			br.Msg = "N值输入错误,请重新输入"
 			return
 		}
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_ZJPJ {
+	case utils.DATA_SOURCE_CALCULATE_ZJPJ:
 		//直接拼接指标
 
 		//校验时间格式
@@ -855,30 +893,75 @@ func (this *CalculateController) BatchEdit() {
 			br.Msg = "拼接日期有误,请重新输入"
 			return
 		}
+	case utils.DATA_SOURCE_CALCULATE_NHCC: //拟合残差指标
+		//指标校验
+		if len(req.EdbInfoIdArr) != 2 {
+			br.Msg = "选择的指标异常,请重新选择"
+			return
+		}
+		fromEdbInfoId = req.EdbInfoIdArr[0].EdbInfoId
+
+		//校验时间格式
+		//数据格式:2022-11-01,2022-11-10
+		timeList := strings.Split(req.Formula, ",")
+		if len(timeList) != 2 {
+			br.Msg = "选择时间有误,请重新输入"
+			return
+		}
+		startDate, err := time.ParseInLocation(utils.FormatDate, timeList[0], time.Local)
+		if err != nil {
+			br.Msg = "开始日期有误,请重新输入"
+			return
+		}
+		endDate, err := time.ParseInLocation(utils.FormatDate, timeList[1], time.Local)
+		if err != nil {
+			br.Msg = "结束日期有误,请重新输入"
+			return
+		}
+		if utils.GetTimeSubDay(startDate, endDate) < 2 {
+			br.Msg = "日期间隔不得少于两天"
+			return
+		}
+		nhccDate.StartDate = startDate
+		nhccDate.EndDate = endDate
+	}
+
+	// 获取基础指标信息
+	fromEdbInfo, err := models.GetEdbInfoById(fromEdbInfoId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "指标已被删除,请刷新页面"
+			br.ErrMsg = "指标已被删除,请刷新页面:Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败:Err:" + err.Error()
+		return
 	}
 
 	var sourName string
 	var edbInfoId int
 
-	if req.Source == utils.DATA_SOURCE_CALCULATE_LJZZY {
+	switch req.Source {
+	case utils.DATA_SOURCE_CALCULATE_LJZZY:
 		sourName = "累计值转月值"
 		if fromEdbInfo.Frequency != "月度" {
 			br.Msg = "请选择月度指标"
 			return
 		}
 		err = models.EditCalculateLjzzy(edbInfo, &req, fromEdbInfo)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_TBZ {
+	case utils.DATA_SOURCE_CALCULATE_TBZ:
 		sourName = "同比值"
 		err = models.EditCalculateTbz(edbInfo, &req, fromEdbInfo)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_TCZ {
+	case utils.DATA_SOURCE_CALCULATE_TCZ:
 		fmt.Println("start edit", time.Now())
 		sourName = "同差值"
 		err = models.EditCalculateTcz(edbInfo, &req, fromEdbInfo)
 		fmt.Println("end edit", time.Now())
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_NSZYDPJJS {
+	case utils.DATA_SOURCE_CALCULATE_NSZYDPJJS:
 		sourName = "N数值移动平均计算"
 		err = models.EditCalculateNszydpjjs(edbInfo, &req, fromEdbInfo, formulaInt, edbInfo.CalculateFormula)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_HBZ {
+	case utils.DATA_SOURCE_CALCULATE_HBZ:
 		var condition string
 		var pars []interface{}
 		condition += " AND edb_info_id =? "
@@ -897,16 +980,16 @@ func (this *CalculateController) BatchEdit() {
 		}
 		sourName = "环比值"
 		err = models.EditCalculateHbz(edbInfo, &req, fromEdbInfo, formulaInt, edbInfo.CalculateFormula)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_HCZ {
+	case utils.DATA_SOURCE_CALCULATE_HCZ:
 		sourName = "环差值"
 		err = models.EditCalculateHcz(edbInfo, &req, fromEdbInfo, formulaInt, edbInfo.CalculateFormula)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_BP {
+	case utils.DATA_SOURCE_CALCULATE_BP:
 		sourName = "变频"
 		err = models.EditCalculateBp(edbInfo, &req, fromEdbInfo)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_TIME_SHIFT {
+	case utils.DATA_SOURCE_CALCULATE_TIME_SHIFT:
 		sourName = "时间移位"
 		err = models.EditCalculateTimeShift(edbInfo, &req, fromEdbInfo)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_ZJPJ {
+	case utils.DATA_SOURCE_CALCULATE_ZJPJ:
 		sourName = "直接拼接"
 
 		if len(req.EdbInfoIdArr) != 1 {
@@ -929,7 +1012,7 @@ func (this *CalculateController) BatchEdit() {
 			return
 		}
 		err = models.EditCalculateZjpj(&req, edbInfo, fromEdbInfo, secondEdbInfo)
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_LJZTBPJ { //累计值同比拼接
+	case utils.DATA_SOURCE_CALCULATE_LJZTBPJ: //累计值同比拼接
 		sourName = "累计值同比拼接"
 
 		if fromEdbInfo.Frequency != "月度" {
@@ -967,14 +1050,30 @@ func (this *CalculateController) BatchEdit() {
 			return
 		}
 		err = models.EditCalculateLjztbpj(&req, edbInfo, fromEdbInfo, tbzEdbInfo)
-
-	} else if req.Source == utils.DATA_SOURCE_CALCULATE_CJJX {
+	case utils.DATA_SOURCE_CALCULATE_CJJX:
 		sourName = "超季节性"
 		err = models.EditCalculateCjjx(&req, edbInfo, fromEdbInfo, formulaInt)
-	} else {
+	case utils.DATA_SOURCE_CALCULATE_NHCC:
+		sourName = "拟合残差"
+		secondEdbInfoReq := req.EdbInfoIdArr[1]
+		secondEdbInfo, err := models.GetEdbInfoById(secondEdbInfoReq.EdbInfoId)
+		if err != nil {
+			br.Msg = "获取因变量的指标信息失败"
+			br.ErrMsg = "获取因变量的指标信息失败:Err:" + err.Error()
+			return
+		}
+
+		if fromEdbInfo.EdbInfoId == secondEdbInfo.EdbInfoId {
+			br.Msg = "两个指标不允许为同一个"
+			br.ErrMsg = "两个指标不允许为同一个"
+			return
+		}
+		err = models.EditCalculateNhcc(&req, edbInfo, fromEdbInfo, secondEdbInfo, nhccDate)
+	default:
 		br.Msg = "无效计算方式"
 		br.ErrMsg = "无效计算方式,source:" + strconv.Itoa(req.Source)
 		return
+
 	}
 	if err != nil {
 		br.Msg = "生成" + sourName + "失败"
@@ -1310,6 +1409,12 @@ func (this *CalculateController) Refresh() {
 			errMsg = "RefreshAllCalculateCjjx Err:" + err.Error()
 			break
 		}
+	case utils.DATA_SOURCE_CALCULATE_NHCC: //nhcc
+		err = models.RefreshAllCalculateNhcc(edbInfo)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			errMsg = "RefreshAllCalculateNhcc Err:" + err.Error()
+			break
+		}
 
 	default:
 		br.Msg = "来源异常,请联系相关开发!"

+ 2 - 0
models/base_from_calculate.go

@@ -584,6 +584,7 @@ type EdbInfoCalculateBatchSaveReq struct {
 	EdbInfoIdArr     []struct {
 		EdbInfoId int    `description:"指标id"`
 		FromTag   string `description:"指标对应标签"`
+		MoveValue int    `description:"移动的值"`
 	}
 	MoveType      int    `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency string `description:"移动频度:天/周/月/季/年"`
@@ -604,6 +605,7 @@ type EdbInfoCalculateBatchEditReq struct {
 	EdbInfoIdArr  []struct {
 		EdbInfoId int    `description:"指标id"`
 		FromTag   string `description:"指标对应标签"`
+		MoveValue int    `description:"移动的值"`
 	}
 }
 

+ 1 - 0
models/db.go

@@ -42,5 +42,6 @@ func init() {
 		new(EdbDataPython),
 		new(ChartEdbMapping),
 		new(PredictEdbConf),
+		new(EdbDataCalculateNhcc),
 	)
 }

+ 610 - 0
models/edb_data_calculate_nhcc.go

@@ -0,0 +1,610 @@
+package models
+
+import (
+	"errors"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/shopspring/decimal"
+	"hongze/hongze_edb_lib/utils"
+	"strings"
+	"time"
+)
+
+// EdbDataCalculateNhcc 拟合残差数据结构体
+type EdbDataCalculateNhcc struct {
+	EdbDataId     int `orm:"column(edb_data_id);pk"`
+	EdbInfoId     int
+	EdbCode       string
+	DataTime      string
+	Value         float64
+	Status        int
+	CreateTime    time.Time
+	ModifyTime    time.Time
+	DataTimestamp int64
+}
+
+// NhccDate 拟合残差的开始、结束日期
+type NhccDate struct {
+	StartDate time.Time
+	EndDate   time.Time
+}
+
+// AddCalculateNhcc 新增拟合残差数据
+func AddCalculateNhcc(req *EdbInfoCalculateBatchSaveReq, firstEdbInfo, secondEdbInfo *EdbInfo, edbCode, uniqueCode string, nhccDate NhccDate, sysUserId int, sysUserRealName string) (edbInfo *EdbInfo, err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("AddCalculateNhcc,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	if req.EdbInfoId <= 0 {
+		edbInfo = &EdbInfo{
+			SourceName:       "拟合残差",
+			Source:           utils.DATA_SOURCE_CALCULATE_NHCC,
+			EdbCode:          edbCode,
+			EdbName:          req.EdbName,
+			EdbNameSource:    req.EdbName,
+			Frequency:        req.Frequency,
+			Unit:             req.Unit,
+			StartDate:        firstEdbInfo.StartDate,
+			EndDate:          firstEdbInfo.EndDate,
+			ClassifyId:       req.ClassifyId,
+			SysUserId:        sysUserId,
+			SysUserRealName:  sysUserRealName,
+			UniqueCode:       uniqueCode,
+			CreateTime:       time.Now(),
+			ModifyTime:       time.Now(),
+			CalculateFormula: req.Formula,
+			EdbType:          2,
+		}
+		newEdbInfoId, tmpErr := to.Insert(edbInfo)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		edbInfo.EdbInfoId = int(newEdbInfoId)
+	} else {
+		edbInfo, err = GetEdbInfoById(req.EdbInfoId)
+		if err != nil {
+			return
+		}
+		//查询
+		tmpEdbInfo, tmpErr := GetEdbInfoById(edbInfo.EdbInfoId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		tmpEdbInfo.EdbName = req.EdbName
+		tmpEdbInfo.ClassifyId = req.ClassifyId
+		tmpEdbInfo.Frequency = req.Frequency
+		tmpEdbInfo.Unit = req.Unit
+		tmpEdbInfo.CalculateFormula = req.Formula
+
+		edbInfo = tmpEdbInfo
+
+		//删除指标数据
+		dataTableName := GetEdbDataTableName(utils.DATA_SOURCE_CALCULATE_NHCC)
+		fmt.Println("dataTableName:" + dataTableName)
+		deleteSql := ` DELETE FROM %s WHERE edb_info_id=? `
+		deleteSql = fmt.Sprintf(deleteSql, dataTableName)
+		_, err = to.Raw(deleteSql, req.EdbInfoId).Exec()
+		if err != nil {
+			return
+		}
+
+		//删除指标关系
+		sql := ` DELETE FROM edb_info_calculate_mapping WHERE edb_info_id=? `
+		_, err = to.Raw(sql, edbInfo.EdbInfoId).Exec()
+	}
+
+	//关联关系
+
+	var existItemA, existItemB *EdbInfoCalculateMapping
+	//第一个指标
+	{
+		existItemA = &EdbInfoCalculateMapping{
+			EdbInfoCalculateMappingId: 0,
+			EdbInfoId:                 edbInfo.EdbInfoId,
+			Source:                    edbInfo.Source,
+			SourceName:                edbInfo.SourceName,
+			EdbCode:                   edbInfo.EdbCode,
+			FromEdbInfoId:             firstEdbInfo.EdbInfoId,
+			FromEdbCode:               firstEdbInfo.EdbCode,
+			FromEdbName:               firstEdbInfo.EdbName,
+			FromSource:                firstEdbInfo.Source,
+			FromSourceName:            firstEdbInfo.SourceName,
+			FromTag:                   "A",
+			MoveValue:                 req.EdbInfoIdArr[0].MoveValue,
+			Sort:                      1,
+			CreateTime:                time.Now(),
+			ModifyTime:                time.Now(),
+		}
+		insertId, tmpErr := to.Insert(existItemA)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		existItemA.EdbInfoCalculateMappingId = int(insertId)
+	}
+
+	//第二个指标
+	{
+		existItemB = &EdbInfoCalculateMapping{
+			EdbInfoCalculateMappingId: 0,
+			EdbInfoId:                 edbInfo.EdbInfoId,
+			Source:                    edbInfo.Source,
+			SourceName:                edbInfo.SourceName,
+			EdbCode:                   edbInfo.EdbCode,
+			FromEdbInfoId:             secondEdbInfo.EdbInfoId,
+			FromEdbCode:               secondEdbInfo.EdbCode,
+			FromEdbName:               secondEdbInfo.EdbName,
+			FromSource:                secondEdbInfo.Source,
+			FromSourceName:            secondEdbInfo.SourceName,
+			FromTag:                   "B",
+			MoveValue:                 req.EdbInfoIdArr[1].MoveValue,
+			Sort:                      1,
+			CreateTime:                time.Now(),
+			ModifyTime:                time.Now(),
+		}
+		insertId, tmpErr := to.Insert(existItemB)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		existItemB.EdbInfoCalculateMappingId = int(insertId)
+	}
+
+	//拼接数据
+	err = refreshAllCalculateNhcc(to, edbInfo, existItemA, existItemB, nhccDate)
+
+	return
+}
+
+// EditCalculateNhcc 编辑拟合残差数据
+func EditCalculateNhcc(req *EdbInfoCalculateBatchEditReq, edbInfo, firstEdbInfo, secondEdbInfo *EdbInfo, nhccDate NhccDate) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("EditCalculateNhcc,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	nowEdbInfo := *edbInfo // 现在的指标信息
+
+	//修改指标信息
+	edbInfo.EdbName = req.EdbName
+	edbInfo.EdbNameSource = req.EdbName
+	edbInfo.Frequency = req.Frequency
+	edbInfo.Unit = req.Unit
+	edbInfo.ClassifyId = req.ClassifyId
+	edbInfo.CalculateFormula = req.Formula
+	edbInfo.ModifyTime = time.Now()
+	_, err = to.Update(edbInfo, "EdbName", "EdbNameSource", "Frequency", "Unit", "ClassifyId", "CalculateFormula", "ModifyTime")
+	if err != nil {
+		return
+	}
+
+	var existCondition string
+	var existPars []interface{}
+	existCondition += " AND edb_info_id=? "
+	existPars = append(existPars, edbInfo.EdbInfoId)
+
+	//查询出所有的关联指标
+	existList, err := GetEdbInfoCalculateListByCondition(existCondition, existPars)
+	if err != nil {
+		err = fmt.Errorf("判断指标是否改变失败,Err:" + err.Error())
+		return
+	}
+
+	var existItemA, existItemB *EdbInfoCalculateMapping
+	for _, existItem := range existList {
+		if existItem.FromTag == "A" {
+			existItemA = existItem
+		} else if existItem.FromTag == "B" {
+			existItemB = existItem
+		}
+	}
+
+	// 是否需要删除数据重新计算
+	isNeedCalculateData := false
+
+	// 如果截止日期变更,那么需要重新计算
+	if req.Formula != nowEdbInfo.CalculateFormula {
+		isNeedCalculateData = true
+	}
+	//第一个指标数据
+	{
+		// 如果指标变了,那么需要删除关系,并重新计算
+		if existItemA.FromEdbInfoId != firstEdbInfo.EdbInfoId || existItemA.MoveValue != req.EdbInfoIdArr[0].MoveValue {
+			//删除之前的A指标关联关系
+			sql := ` DELETE FROM edb_info_calculate_mapping WHERE edb_info_id = ? and from_edb_info_id = ?`
+			_, err = to.Raw(sql, edbInfo.EdbInfoId, existItemA.FromEdbInfoId).Exec()
+			if err != nil {
+				err = fmt.Errorf("删除拼接日期之前的指标关联关系失败,Err:" + err.Error())
+				return
+			}
+
+			//添加新的指标关系
+			{
+				existItemA = &EdbInfoCalculateMapping{
+					EdbInfoCalculateMappingId: 0,
+					EdbInfoId:                 edbInfo.EdbInfoId,
+					Source:                    edbInfo.Source,
+					SourceName:                edbInfo.SourceName,
+					EdbCode:                   edbInfo.EdbCode,
+					FromEdbInfoId:             firstEdbInfo.EdbInfoId,
+					FromEdbCode:               firstEdbInfo.EdbCode,
+					FromEdbName:               firstEdbInfo.EdbName,
+					FromSource:                firstEdbInfo.Source,
+					FromSourceName:            firstEdbInfo.SourceName,
+					FromTag:                   "A",
+					MoveValue:                 req.EdbInfoIdArr[0].MoveValue,
+					Sort:                      1,
+					CreateTime:                time.Now(),
+					ModifyTime:                time.Now(),
+				}
+				insertId, tmpErr := to.Insert(existItemA)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				existItemA.EdbInfoCalculateMappingId = int(insertId)
+
+				isNeedCalculateData = true
+			}
+		}
+	}
+
+	//第二个指标数据
+	{
+		// 如果指标变了,那么需要删除关系,并重新计算
+		if existItemB.FromEdbInfoId != secondEdbInfo.EdbInfoId || existItemB.MoveValue != req.EdbInfoIdArr[1].MoveValue {
+			//删除之前的B指标关联关系
+			sql := ` DELETE FROM edb_info_calculate_mapping WHERE edb_info_id = ? and from_edb_info_id = ?`
+			_, err = to.Raw(sql, edbInfo.EdbInfoId, existItemB.FromEdbInfoId).Exec()
+			if err != nil {
+				err = fmt.Errorf("删除拼接日期之后的指标关联关系失败,Err:" + err.Error())
+				return
+			}
+
+			// 添加新的指标关联关系
+			existItemB = &EdbInfoCalculateMapping{
+				EdbInfoCalculateMappingId: 0,
+				EdbInfoId:                 edbInfo.EdbInfoId,
+				Source:                    edbInfo.Source,
+				SourceName:                edbInfo.SourceName,
+				EdbCode:                   edbInfo.EdbCode,
+				FromEdbInfoId:             secondEdbInfo.EdbInfoId,
+				FromEdbCode:               secondEdbInfo.EdbCode,
+				FromEdbName:               secondEdbInfo.EdbName,
+				FromSource:                secondEdbInfo.Source,
+				FromSourceName:            secondEdbInfo.SourceName,
+				FromTag:                   "B",
+				MoveValue:                 req.EdbInfoIdArr[1].MoveValue,
+				Sort:                      2,
+				CreateTime:                time.Now(),
+				ModifyTime:                time.Now(),
+			}
+			insertId, tmpErr := to.Insert(existItemB)
+			if tmpErr != nil {
+				err = tmpErr
+				return
+			}
+			existItemA.EdbInfoCalculateMappingId = int(insertId)
+
+			isNeedCalculateData = true
+		}
+	}
+
+	// 如果需要重新计算,那么先删除所有的指标数据,然后再重新计算
+	if isNeedCalculateData {
+		// 删除之前所有的指标数据
+
+		tableName := GetEdbDataTableName(edbInfo.Source)
+		sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? `, tableName)
+		_, err = to.Raw(sql, edbInfo.EdbInfoId).Exec()
+		if err != nil {
+			err = fmt.Errorf("删除历史数据失败,Err:" + err.Error())
+			return
+		}
+
+		err = refreshAllCalculateNhcc(to, edbInfo, existItemA, existItemB, nhccDate)
+	}
+
+	return
+}
+
+// RefreshAllCalculateNhcc 刷新所有 拟合残差 数据
+func RefreshAllCalculateNhcc(edbInfo *EdbInfo) (err error) {
+	o := orm.NewOrm()
+	to, err := o.Begin()
+	defer func() {
+		if err != nil {
+			fmt.Println("RefreshAllCalculateNhcc,Err:" + err.Error())
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+	//查询关联指标信息
+	var existCondition string
+	var existPars []interface{}
+	existCondition += " AND edb_info_id=? "
+	existPars = append(existPars, edbInfo.EdbInfoId)
+	existList, err := GetEdbInfoCalculateListByCondition(existCondition, existPars)
+	if err != nil {
+		err = fmt.Errorf("判断指标是否改变失败,Err:" + err.Error())
+		return
+	}
+
+	var existItemA, existItemB *EdbInfoCalculateMapping
+	for _, existItem := range existList {
+		if existItem.FromTag == "A" {
+			existItemA = existItem
+		} else if existItem.FromTag == "B" {
+			existItemB = existItem
+		}
+	}
+
+	timeList := strings.Split(edbInfo.CalculateFormula, ",")
+	startDate, err := time.ParseInLocation(utils.FormatDate, timeList[0], time.Local)
+	if err != nil {
+		return
+	}
+	endDate, err := time.ParseInLocation(utils.FormatDate, timeList[1], time.Local)
+	if err != nil {
+		return
+	}
+	if endDate.Sub(startDate).Hours() < 48 {
+		return
+	}
+	nhccDate := NhccDate{
+		StartDate: startDate,
+		EndDate:   endDate,
+	}
+	// 刷新数据
+	err = refreshAllCalculateNhcc(to, edbInfo, existItemA, existItemB, nhccDate)
+
+	return
+}
+
+// refreshAllCalculateNhcc 刷新所有 拟合残差 数据
+func refreshAllCalculateNhcc(to orm.TxOrmer, edbInfo *EdbInfo, existItemA, existItemB *EdbInfoCalculateMapping, nhccDate NhccDate) (err error) {
+	//查询当前指标现有的数据
+	var condition string
+	var pars []interface{}
+	condition += " AND edb_info_id=? "
+	pars = append(pars, edbInfo.EdbInfoId)
+
+	var dataList []*EdbDataCalculateNhcc
+	sql := ` SELECT * FROM edb_data_calculate_nhcc WHERE edb_info_id=? ORDER BY data_time DESC `
+	_, err = to.Raw(sql, edbInfo.EdbInfoId).QueryRows(&dataList)
+	if err != nil {
+		return err
+	}
+	var dateArr []string
+	dataMap := make(map[string]*EdbDataCalculateNhcc)
+	removeDataTimeMap := make(map[string]int) //需要移除的日期数据
+	for _, v := range dataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+		removeDataTimeMap[v.DataTime] = 1
+	}
+
+	addDataList := make([]*EdbDataCalculateNhcc, 0)
+
+	//第一个指标
+	aDataList := make([]EdbInfoSearchData, 0)
+	aDataMap := make(map[string]float64)
+	{
+		var condition string
+		var pars []interface{}
+
+		condition += " AND edb_info_id=? "
+		pars = append(pars, existItemA.FromEdbInfoId)
+
+		//第一个指标的数据列表
+		firstDataList, tmpErr := GetEdbDataListAllByTo(to, condition, pars, existItemA.FromSource, 0)
+		if tmpErr != nil {
+			return tmpErr
+		}
+		aDataList, aDataMap = handleNhccData(firstDataList, existItemA.MoveValue)
+
+	}
+
+	//第二个指标
+	bDataList := make([]EdbInfoSearchData, 0)
+	bDataMap := make(map[string]float64)
+	{
+		condition = ``
+		pars = make([]interface{}, 0)
+
+		condition += "  AND edb_info_id = ? "
+		pars = append(pars, edbInfo.CalculateFormula, existItemB.FromEdbInfoId)
+
+		//第二个指标的数据列表
+		secondDataList, tmpErr := GetEdbDataListAllByTo(to, condition, pars, existItemB.FromSource, 0)
+		if tmpErr != nil {
+			return tmpErr
+		}
+		bDataList, bDataMap = handleNhccData(secondDataList, existItemB.MoveValue)
+	}
+
+	// 计算线性方程公式
+	var a, b float64
+	{
+		coordinateData := make([]Coordinate, 0)
+		for i := nhccDate.StartDate; i.Before(nhccDate.EndDate) || i.Equal(nhccDate.EndDate); i.AddDate(0, 0, 1) {
+			dateStr := i.Format(utils.FormatDate)
+			xValue, ok := aDataMap[dateStr]
+			if !ok {
+				err = errors.New("指标A日期:" + dateStr + "数据异常,导致计算线性方程是失败")
+				return
+			}
+			yValue, ok := bDataMap[dateStr]
+			if !ok {
+				err = errors.New("指标B日期:" + dateStr + "数据异常,导致计算线性方程是失败")
+				return
+			}
+			tmpCoordinate := Coordinate{
+				X: xValue,
+				Y: yValue,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate)
+		}
+		a, b = getLinearResult(coordinateData)
+	}
+
+	//fmt.Println("a:", a, ";======b:", b)
+
+	//计算B’
+	newBDataMap := make(map[string]float64)
+	{
+		//B’=aA+b
+		aDecimal := decimal.NewFromFloat(a)
+		bDecimal := decimal.NewFromFloat(b)
+		for _, aData := range aDataList {
+			xDecimal := decimal.NewFromFloat(aData.Value)
+			val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).RoundCeil(4).Float64()
+			newBDataMap[aData.DataTime] = val
+		}
+
+	}
+
+	// Delta=B-B‘
+	for _, bData := range bDataList {
+		currDate := bData.DataTime
+		//校验待删除日期数据里面是否存在该元素,如果存在的话,那么移除该元素
+		if _, ok := removeDataTimeMap[currDate]; ok {
+			delete(removeDataTimeMap, currDate)
+		}
+
+		b2Val, ok := newBDataMap[currDate]
+		if !ok {
+			// 如果B`不存在数据,那么就退出当前循环
+			continue
+		}
+		bDecimal := decimal.NewFromFloat(bData.Value)
+		b2Decimal := decimal.NewFromFloat(b2Val)
+
+		val, _ := bDecimal.Sub(b2Decimal).RoundCeil(4).Float64()
+
+		// 判断之前有没有该数据
+		existData, ok := dataMap[currDate]
+		if !ok { //不存在那么就添加吧
+			currentDate, _ := time.Parse(utils.FormatDate, currDate)
+			timestamp := currentDate.UnixNano() / 1e6
+			edbDataNhcc := &EdbDataCalculateNhcc{
+				EdbInfoId:     edbInfo.EdbInfoId,
+				EdbCode:       edbInfo.EdbCode,
+				DataTime:      currDate,
+				Value:         val,
+				Status:        1,
+				CreateTime:    time.Now(),
+				ModifyTime:    time.Now(),
+				DataTimestamp: timestamp,
+			}
+			addDataList = append(addDataList, edbDataNhcc)
+		} else {
+			// 如果有的话,还需要判断值是否一致,一致则不处理,不一致则修改
+			if existData.Value != val {
+				existData.Value = val
+				_, err = to.Update(existData, "Value")
+				if err != nil {
+					return
+				}
+			}
+		}
+	}
+
+	//删除已经不存在的累计同比拼接指标数据(由于同比值当日的数据删除了)
+	{
+		removeDateList := make([]string, 0)
+		for dateTime := range removeDataTimeMap {
+			removeDateList = append(removeDateList, dateTime)
+		}
+		removeNum := len(removeDateList)
+		if removeNum > 0 {
+			//如果拼接指标变更了,那么需要删除所有的指标数据
+			tableName := GetEdbDataTableName(edbInfo.Source)
+			sql := fmt.Sprintf(` DELETE FROM %s WHERE edb_info_id = ? and data_time in (`+utils.GetOrmInReplace(removeNum)+`) `, tableName)
+			_, err = to.Raw(sql, edbInfo.EdbInfoId, removeDateList).Exec()
+			if err != nil {
+				err = fmt.Errorf("删除不存在的拟合残差指标数据失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+
+	//数据入库
+	if len(addDataList) > 0 {
+		_, tmpErr := to.InsertMulti(len(addDataList), addDataList)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+	}
+	return
+}
+
+// handleNhccData 处理拟合残差需要的数据
+func handleNhccData(dataList []*EdbInfoSearchData, moveDay int) (newDataList []EdbInfoSearchData, dateDataMap map[string]float64) {
+	dateMap := make(map[time.Time]float64)
+	var minDate, maxDate time.Time
+	//dateList := make([]string, 0)
+
+	for _, v := range dataList {
+		currDate, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		if minDate.IsZero() || currDate.Before(minDate) {
+			minDate = currDate
+		}
+		if maxDate.IsZero() || currDate.After(maxDate) {
+			maxDate = currDate
+		}
+		dateMap[currDate] = v.Value
+	}
+
+	// 处理领先、滞后数据
+	newDateMap := make(map[time.Time]float64)
+	for currDate, value := range dateMap {
+		newDate := currDate.AddDate(0, 0, moveDay)
+		newDateMap[newDate] = value
+	}
+	minDate = minDate.AddDate(0, 0, moveDay)
+	maxDate = maxDate.AddDate(0, 0, moveDay)
+
+	// 开始变频
+	dayNum := utils.GetTimeSubDay(minDate, maxDate)
+
+	for i := 0; i <= dayNum; i++ {
+		currDate := minDate.AddDate(0, 0, i)
+		tmpValue, ok := newDateMap[currDate]
+		if !ok {
+			//找不到数据,那么就用前面的数据吧
+			tmpValue = newDataList[len(newDataList)-1].Value
+		}
+		tmpData := EdbInfoSearchData{
+			//EdbDataId: 0,
+			DataTime: currDate.Format(utils.FormatDate),
+			Value:    tmpValue,
+		}
+		dateDataMap[tmpData.DataTime] = tmpData.Value
+		newDataList = append(newDataList, tmpData)
+	}
+
+	return
+}

+ 2 - 0
models/edb_data_table.go

@@ -75,6 +75,8 @@ func GetEdbDataTableName(source int) (tableName string) {
 		tableName = "edb_data_calculate_cjjx"
 	case utils.DATA_SOURCE_EIA_STEO:
 		tableName = "edb_data_eia_steo"
+	case utils.DATA_SOURCE_CALCULATE_NHCC:
+		tableName = "edb_data_calculate_nhcc"
 	default:
 		tableName = ""
 	}

+ 1 - 0
models/edb_info_calculate_mapping.go

@@ -18,6 +18,7 @@ type EdbInfoCalculateMapping struct {
 	FromSource                int       `description:"基础指标来源"`
 	FromSourceName            string    `description:"基础指标来源名称"`
 	FromTag                   string    `description:"来源指标标签"`
+	MoveValue                 int       `description:"移动的值,小于0是提前,0是不变,大于0是滞后"`
 	Sort                      int       `description:"计算指标名称排序"`
 	CreateTime                time.Time `description:"创建时间"`
 	ModifyTime                time.Time `description:"修改时间"`

+ 2 - 3
models/predict_edb_info_rule.go

@@ -493,14 +493,13 @@ func GetChartPredictEdbInfoDataListByRuleNLinearRegression(edbInfoId int, nValue
 	a, b := getLinearResult(coordinateData)
 	//fmt.Println("a:", a, ";======b:", b)
 
+	aDecimal := decimal.NewFromFloat(a)
+	bDecimal := decimal.NewFromFloat(b)
 	dayList := getPredictEdbDayList(startDate, endDate, frequency)
 	for k, currentDate := range dayList {
 		tmpK := nValue + k + 1
 
-		aDecimal := decimal.NewFromFloat(a)
 		xDecimal := decimal.NewFromInt(int64(tmpK))
-		bDecimal := decimal.NewFromFloat(b)
-
 		val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).RoundCeil(4).Float64()
 
 		currentDateStr := currentDate.Format(utils.FormatDate)

+ 23 - 0
utils/common.go

@@ -1084,3 +1084,26 @@ func GetOrmInReplace(num int) string {
 	}
 	return strings.Join(template, ",")
 }
+
+// GetTimeSubDay 计算两个时间的自然日期差
+func GetTimeSubDay(t1, t2 time.Time) int {
+	var day int
+	swap := false
+	if t1.Unix() > t2.Unix() {
+		t1, t2 = t2, t1
+		swap = true
+	}
+
+	t1_ := t1.Add(time.Duration(t2.Sub(t1).Milliseconds()%86400000) * time.Millisecond)
+	day = int(t2.Sub(t1).Hours() / 24)
+	// 计算在t1+两个时间的余数之后天数是否有变化
+	if t1_.Day() != t1.Day() {
+		day += 1
+	}
+
+	if swap {
+		day = -day
+	}
+
+	return day
+}

+ 1 - 0
utils/constants.go

@@ -64,6 +64,7 @@ const (
 	DATA_SOURCE_MYSTEEL_CHEMICAL                 //钢联化工->34
 	DATA_SOURCE_CALCULATE_CJJX                   //超季节性->35
 	DATA_SOURCE_EIA_STEO                         //eia steo报告->36
+	DATA_SOURCE_CALCULATE_NHCC                   //计算指标(拟合残差)->37
 )
 
 //基础数据初始化日期