Ver código fonte

Merge branch 'chart/13.1'

hsun 2 anos atrás
pai
commit
2239b0c1ca

+ 210 - 0
controller/chart/chart_common.go

@@ -12,10 +12,12 @@ import (
 	"hongze/hongze_yb/models/response/chart_info"
 	chartEdbMappingModel "hongze/hongze_yb/models/tables/chart_edb_mapping"
 	chartInfoModel "hongze/hongze_yb/models/tables/chart_info"
+	"hongze/hongze_yb/models/tables/chart_info_correlation"
 	"hongze/hongze_yb/models/tables/chart_info_log"
 	"hongze/hongze_yb/models/tables/yb_my_chart"
 	"hongze/hongze_yb/services/alarm_msg"
 	"hongze/hongze_yb/services/chart"
+	"hongze/hongze_yb/services/chart/correlation"
 	future_goodServ "hongze/hongze_yb/services/chart/future_good"
 	"hongze/hongze_yb/services/user"
 	"hongze/hongze_yb/utils"
@@ -77,6 +79,14 @@ func CommonChartInfoDetailFromUniqueCode(c *gin.Context) {
 		}
 		response.OkData("获取成功", resp, c)
 		return
+	case utils.CHART_SOURCE_CORRELATION:
+		resp, isOk, msg, errMsg := getCorrelationChartInfoDetail(chartInfo, myChartClassifyId, user.GetInfoByClaims(c))
+		if !isOk {
+			response.FailMsg(msg, errMsg, c)
+			return
+		}
+		response.OkData("获取成功", resp, c)
+		return
 	default:
 		msg := "错误的图表"
 		errMsg := "错误的图表"
@@ -375,3 +385,203 @@ func FutureGoodChartInfoSave(c *gin.Context) {
 
 	response.OkData("操作成功", "", c)
 }
+
+// getCorrelationChartInfoDetail 获取相关性图表详情
+func getCorrelationChartInfoDetail(chartInfo *chartInfoModel.ChartInfoView, myChartClassifyId int, userInfo user.UserInfo) (resp *chart_info.ChartInfoDetailResp, isOk bool, msg, errMsg string) {
+	resp = new(chart_info.ChartInfoDetailResp)
+	// 获取图表信息
+	//var err error
+	chartInfoId := chartInfo.ChartInfoId
+	chartInfo.ChartSource = "弘则研究"
+
+	startDate := chartInfo.StartDate
+	endDate := chartInfo.EndDate
+
+	// 兼容日期错误
+	{
+		if strings.Count(startDate, "-") == 1 {
+			startDate = startDate + "-01"
+		}
+		if strings.Count(endDate, "-") == 1 {
+			endDate = endDate + "-01"
+		}
+	}
+
+	// 相关性图表信息
+	correlationChart := new(chart_info_correlation.ChartInfoCorrelation)
+	if e := correlationChart.GetItemById(chartInfoId); e != nil {
+		msg = "获取失败"
+		errMsg = "获取图表相关性信息失败, Err:" + e.Error()
+		return
+	}
+	if correlationChart.PeriodData == "" || correlationChart.CorrelationData == "" {
+		msg = "获取失败"
+		errMsg = "相关性图表数据有误"
+		return
+	}
+	//chartInfo.CorrelationLeadUnit = correlationChart.LeadUnit
+	xData := make([]int, 0)
+	yData := make([]float64, 0)
+	if e := json.Unmarshal([]byte(correlationChart.PeriodData), &xData); e != nil {
+		msg = "获取失败"
+		errMsg = "相关性图表X轴数据有误, Err:" + e.Error()
+		return
+	}
+	if e := json.Unmarshal([]byte(correlationChart.CorrelationData), &yData); e != nil {
+		msg = "获取失败"
+		errMsg = "相关性图表X轴数据有误, Err:" + e.Error()
+		return
+	}
+
+	// 获取指标信息
+	edbInfoMappingA, e := chartEdbMappingModel.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdFirst)
+	if e != nil {
+		msg = "获取失败"
+		errMsg = "获取相关性图表, A指标mapping信息失败, Err:" + e.Error()
+		return
+	}
+	edbInfoMappingB, e := chartEdbMappingModel.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
+	if e != nil {
+		msg = "获取失败"
+		errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
+		return
+	}
+
+	xEdbIdValue := xData
+	yDataList := make([]chart_info.YData, 0)
+	yDate := "0000-00-00"
+	yDataList = append(yDataList, chart_info.YData{
+		Date:  yDate,
+		Value: yData,
+	})
+
+	// 完善指标信息
+	edbList, e := correlation.GetChartEdbInfoFormat(chartInfo.ChartInfoId, edbInfoMappingA, edbInfoMappingB)
+	if e != nil {
+		msg = "获取失败"
+		errMsg = "获取相关性图表, 完善指标信息失败, Err:" + e.Error()
+		return
+	}
+	baseEdbInfo := edbList[0] //现货指标
+	chartInfo.UnitEn = baseEdbInfo.UnitEn
+
+	correlationInfo := new(chart_info.CorrelationInfo)
+	correlationInfo.LeadValue = correlationChart.LeadValue
+	correlationInfo.LeadUnit = correlationChart.LeadUnit
+	correlationInfo.StartDate = correlationChart.StartDate.Format(utils.FormatDate)
+	correlationInfo.EndDate = correlationChart.EndDate.Format(utils.FormatDate)
+	correlationInfo.LeadValue = correlationChart.LeadValue
+	correlationInfo.EdbInfoIdFirst = correlationChart.EdbInfoIdFirst
+	correlationInfo.EdbInfoIdSecond = correlationChart.EdbInfoIdSecond
+
+	// 访问记录-仅普通用户记录
+	ok, _, _ := user.GetAdminByUserInfo(userInfo)
+	if !ok {
+		go chart.SaveChartVisitLog(userInfo, chartInfo, myChartClassifyId)
+	}
+
+	// 用户是否有收藏该图表
+	{
+		ob := new(yb_my_chart.YbMyChart)
+		cond := `user_id = ? AND chart_info_id = ?`
+		pars := make([]interface{}, 0)
+		pars = append(pars, userInfo.UserID, chartInfo.ChartInfoId)
+		exists, e := ob.FetchByCondition(cond, pars)
+		if e != nil && e != utils.ErrNoRow {
+			msg = `操作失败`
+			errMsg = "获取用户图表失败, Err: " + e.Error()
+			return
+		}
+		myChartInfo := new(responseModel.MyChartItem)
+		if exists != nil && exists.MyChartID > 0 {
+			myChartInfo.MyChartID = exists.MyChartID
+			myChartInfo.MyChartClassifyID = exists.MyChartClassifyID
+			myChartInfo.ChartInfoID = exists.ChartInfoID
+			myChartInfo.ChartName = exists.ChartName
+			myChartInfo.UniqueCode = exists.UniqueCode
+			myChartInfo.ChartImage = exists.ChartImage
+			myChartInfo.UserID = exists.UserID
+			myChartInfo.ReportID = exists.ReportID
+			myChartInfo.ReportChapterID = exists.ReportChapterID
+			myChartInfo.CreateTime = utils.TimeTransferString(utils.FormatDateTime, exists.CreateTime)
+		}
+
+		resp.MyChartInfo = myChartInfo
+	}
+
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.CorrelationChartInfo = correlationInfo
+	isOk = true
+	return
+}
+
+// RefreshCorrelationChartInfo 刷新相关性图表信息
+// @Tags 图库模块
+// @Summary  刷新图表信息
+// @Description 刷新图表信息
+// @Security ApiKeyAuth
+// @Param Authorization	header string true "Bearer 31a165baebe6dec616b1f8f3207b4273"
+// @Accept  json
+// @Product json
+// @Param data body chartInfoModel.SaveChartInfoReq true "请求参数"
+// @Success 200 {string} string "操作成功"
+// @failure 400 {string} string "操作失败"
+// @Router /my_chart/correlation/refreshChartInfo [post]
+func RefreshCorrelationChartInfo(c *gin.Context) {
+	// 参数校验
+	var req chartInfoModel.RefreshChartInfoReq
+	if c.ShouldBind(&req) != nil {
+		response.Fail("参数异常", c)
+		return
+	}
+	chartInfoId := req.ChartInfoId
+	if chartInfoId == 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+
+	userInfo := user.GetInfoByClaims(c)
+	ok, _, err := user.GetAdminByUserInfo(userInfo)
+	if err != nil {
+		response.FailMsg("刷新失败", "RefreshChartInfo-获取系统用户信息失败"+err.Error(), c)
+		return
+	}
+	if !ok {
+		// 普通用户刷新频率限制-每个用户/图/天/2次
+		cacheKey := utils.HZ_CHART_LIB_DETAIL + "YB_REFRESH_LIMIT_" + strconv.Itoa(chartInfoId) + "_" + strconv.Itoa(int(userInfo.UserID))
+		countUserRefresh, _ := global.Redis.Get(context.TODO(), cacheKey).Int()
+		if countUserRefresh >= 2 {
+			response.Ok("目前已是最新数据", c)
+			return
+		}
+		countUserRefresh += 1
+		now := time.Now()
+		today := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 0, time.Local)
+		sub := today.Sub(now)
+		_ = global.Redis.SetEX(context.TODO(), cacheKey, countUserRefresh, sub)
+	}
+
+	// 图表信息校验
+	chartInfo, err := chartInfoModel.GetChartInfoById(chartInfoId)
+	if err != nil {
+		if err == utils.ErrNoRow {
+			response.Fail("图表已被删除,无需刷新", c)
+			return
+		}
+		response.FailMsg("刷新失败", "刷新失败, Err:"+err.Error(), c)
+		return
+	}
+
+	// 刷新图表
+	_ = correlation.ChartInfoRefresh(chartInfo.ChartInfoId)
+
+	//清除图表缓存
+	{
+		key := utils.HZ_CHART_LIB_DETAIL + chartInfo.UniqueCode
+		_ = global.Redis.Del(context.TODO(), key)
+	}
+	response.OkData("刷新成功", "", c)
+}

+ 18 - 6
models/response/chart_info/chart_info.go

@@ -7,12 +7,13 @@ import (
 )
 
 type ChartInfoDetailResp struct {
-	ChartInfo   *chart_info.ChartInfoView
-	EdbInfoList []*chart_edb_mapping.ChartEdbInfoMappingList
-	XEdbIdValue []int   `description:"柱方图的x轴数据,指标id"`
-	XDataList   []XData `description:"商品价格曲线的X轴数据"`
-	YDataList   []YData `description:"柱方图的y轴数据"`
-	MyChartInfo *responseModel.MyChartItem
+	ChartInfo            *chart_info.ChartInfoView
+	EdbInfoList          []*chart_edb_mapping.ChartEdbInfoMappingList
+	XEdbIdValue          []int   `description:"柱方图的x轴数据,指标id"`
+	XDataList            []XData `description:"商品价格曲线的X轴数据"`
+	YDataList            []YData `description:"柱方图的y轴数据"`
+	MyChartInfo          *responseModel.MyChartItem
+	CorrelationChartInfo *CorrelationInfo `description:"相关性图表信息"`
 }
 
 // XData 商品价格曲线的的x轴数据
@@ -31,3 +32,14 @@ type YData struct {
 	NoDataEdbList  []int     `description:"没有数据的指标列表"`
 	XEdbInfoIdList []int     `description:"对应X轴的指标id列表"`
 }
+
+type CorrelationInfo struct {
+	LeadValue       int    `description:"领先值"`
+	LeadUnit        string `description:"领先单位"`
+	StartDate       string `description:"开始日期"`
+	EndDate         string `description:"结束日期"`
+	EdbInfoIdFirst  int    `description:"A指标ID"`
+	EdbInfoIdSecond int    `description:"B指标ID"`
+	PeriodData      string `description:"X轴-期数数据"`
+	CorrelationData string `description:"Y轴-相关性系数"`
+}

+ 9 - 0
models/tables/chart_edb_mapping/query.go

@@ -98,3 +98,12 @@ func GetFutureGoodEdbChartEdbMapping(chartInfoId int) (item *ChartEdbInfoMapping
 	err = global.MYSQL["data"].Raw(sql, chartInfoId, utils.CHART_SOURCE_FUTURE_GOOD).First(&item).Error
 	return
 }
+
+// GetChartEdbMappingByEdbInfoId 根据指标id获取edb_mapping
+func GetChartEdbMappingByEdbInfoId(edbInfoId int) (item *ChartEdbInfoMapping, err error) {
+	sql := ` SELECT edb_info_id,source_name,source,edb_code,edb_name,edb_name_en,frequency,unit,unit_en,start_date,end_date,modify_time,latest_date,latest_value,unique_code,edb_info_type AS edb_info_category_type,max_value,min_value
+             FROM edb_info
+			 WHERE edb_info_id = ? limit 1`
+	err = global.MYSQL["data"].Raw(sql, edbInfoId).First(&item).Error
+	return
+}

+ 50 - 0
models/tables/chart_info_correlation/entity.go

@@ -0,0 +1,50 @@
+package chart_info_correlation
+
+import "time"
+
+// ChartInfoCorrelation 相关性图表-扩展信息
+type ChartInfoCorrelation struct {
+	CorrelationChartInfoId int       `gorm:"primaryKey;column:correlation_chart_info_id;type:int(10) unsigned;not null" json:"-"`       // 相关性图表ID(chart_info表source=3的)
+	LeadValue              int       `gorm:"column:lead_value;type:int(10) unsigned;not null;default:0" json:"leadValue"`               // 领先值
+	LeadUnit               string    `gorm:"column:lead_unit;type:varchar(16);not null;default:''" json:"leadUnit"`                     // 领先单位
+	StartDate              time.Time `gorm:"column:start_date;type:date" json:"startDate"`                                              // 开始日期
+	EndDate                time.Time `gorm:"column:end_date;type:date" json:"endDate"`                                                  // 结束日期
+	EdbInfoIdFirst         int       `gorm:"column:edb_info_id_first;type:int(10) unsigned;not null;default:0" json:"edbInfoIdFirst"`   // A指标ID
+	EdbInfoIdSecond        int       `gorm:"column:edb_info_id_second;type:int(10) unsigned;not null;default:0" json:"edbInfoIdSecond"` // B指标ID
+	PeriodData             string    `gorm:"column:period_data;type:text" json:"periodData"`                                            // X轴-期数数据
+	CorrelationData        string    `gorm:"column:correlation_data;type:text" json:"correlationData"`                                  // Y轴-相关性系数
+	CreateTime             time.Time `gorm:"column:create_time;type:datetime" json:"createTime"`                                        // 创建时间
+	ModifyTime             time.Time `gorm:"column:modify_time;type:datetime" json:"modifyTime"`                                        // 更新时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *ChartInfoCorrelation) TableName() string {
+	return "chart_info_correlation"
+}
+
+// ChartInfoCorrelationColumns get sql column name.获取数据库列名
+var ChartInfoCorrelationColumns = struct {
+	CorrelationChartInfoID string
+	LeadValue              string
+	LeadUnit               string
+	StartDate              string
+	EndDate                string
+	EdbInfoIDFirst         string
+	EdbInfoIDSecond        string
+	PeriodData             string
+	CorrelationData        string
+	CreateTime             string
+	ModifyTime             string
+}{
+	CorrelationChartInfoID: "correlation_chart_info_id",
+	LeadValue:              "lead_value",
+	LeadUnit:               "lead_unit",
+	StartDate:              "start_date",
+	EndDate:                "end_date",
+	EdbInfoIDFirst:         "edb_info_id_first",
+	EdbInfoIDSecond:        "edb_info_id_second",
+	PeriodData:             "period_data",
+	CorrelationData:        "correlation_data",
+	CreateTime:             "create_time",
+	ModifyTime:             "modify_time",
+}

+ 16 - 0
models/tables/chart_info_correlation/model.go

@@ -0,0 +1,16 @@
+package chart_info_correlation
+
+import "hongze/hongze_yb/global"
+
+// GetItemById 主键获取
+func (m *ChartInfoCorrelation) GetItemById(primaryId int) (err error) {
+	err = global.MYSQL["data"].Model(m).
+		Where("correlation_chart_info_id = ?", primaryId).
+		First(&m).Error
+	return
+}
+
+func (m *ChartInfoCorrelation) Update(updateCols []string) (err error) {
+	err = global.MYSQL["data"].Model(m).Select(updateCols).Updates(*m).Error
+	return
+}

+ 1 - 1
models/tables/edb_data/query.go

@@ -15,7 +15,7 @@ func GetEdbDataTableName(source int) (tableName string) {
 		tableName = "edb_data_ths"
 	case utils.DATA_SOURCE_WIND:
 		tableName = "edb_data_wind"
-	case utils.DATA_SOURCE_PB, utils.DATA_SOURCE_PB_FINANCE:
+	case utils.DATA_SOURCE_PB, utils.DATA_SOURCE_PB_FINANCE: //彭博经济数据、彭博财务数据
 		tableName = "edb_data_pb"
 	case utils.DATA_SOURCE_CALCULATE:
 		tableName = "edb_data_calculate"

+ 40 - 0
models/tables/edb_info/query.go

@@ -1,8 +1,11 @@
 package edb_info
 
 import (
+	"fmt"
 	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/tables/edb_data"
 	"hongze/hongze_yb/utils"
+	"time"
 )
 
 // GetEdbInfoById 主键获取指标信息
@@ -81,3 +84,40 @@ func GetPredictEdbInfoAllCalculate(edbInfoIdList []int) (list []*EdbInfo, err er
 
 	return
 }
+
+type EdbInfoSearchData struct {
+	DataTime string  `description:"数据日期"`
+	Value    float64 `description:"数据"`
+}
+
+// GetEdbDataListAll order:1升序,其余值为降序
+func GetEdbDataListAll(condition string, pars []interface{}, source, order int) (items []*EdbInfoSearchData, err error) {
+	items = make([]*EdbInfoSearchData, 0)
+	sql := ``
+	tableName := edb_data.GetEdbDataTableName(source)
+	sql = ` SELECT * FROM %s WHERE 1=1 `
+	sql = fmt.Sprintf(sql, tableName)
+
+	if condition != "" {
+		sql += condition
+	}
+	if order == 1 {
+		sql += ` ORDER BY data_time ASC `
+	} else {
+		sql += ` ORDER BY data_time DESC `
+	}
+
+	type EdbInfoSearchDataOriginTime struct {
+		DataTime time.Time `description:"数据日期"`
+		Value    float64   `description:"数据"`
+	}
+	queryList := make([]*EdbInfoSearchDataOriginTime, 0)
+	err = global.MYSQL["data"].Raw(sql, pars...).Scan(&queryList).Error
+	for i := range queryList {
+		items = append(items, &EdbInfoSearchData{
+			DataTime: queryList[i].DataTime.Format(utils.FormatDate),
+			Value:    queryList[i].Value,
+		})
+	}
+	return
+}

+ 2 - 0
routers/chart.go

@@ -18,6 +18,7 @@ func InitChart(r *gin.Engine) {
 		rGroup.POST("/refreshChartInfo", chart.RefreshChartInfo)
 		rGroup.GET("/getChartBeforeAndNext", chart.GetChartBeforeAndNext)
 		rGroup.POST("/future_good/refreshChartInfo", chart.RefreshFutureGoodChartInfo)
+		rGroup.POST("/correlation/refreshChartInfo", chart.RefreshCorrelationChartInfo)
 	}
 
 	initChart(r)
@@ -37,5 +38,6 @@ func initChart(r *gin.Engine) {
 		rGroup.GET("/getChartBeforeAndNext", chart.GetChartBeforeAndNext)
 		rGroup.POST("/future_good/refreshChartInfo", chart.RefreshFutureGoodChartInfo)
 		rGroup.POST("/future_good/editChartInfo", chart.FutureGoodChartInfoSave)
+		rGroup.POST("/correlation/refreshChartInfo", chart.RefreshCorrelationChartInfo)
 	}
 }

+ 385 - 0
services/chart/correlation/chart_info.go

@@ -0,0 +1,385 @@
+package correlation
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"hongze/hongze_yb/models/response/chart_info"
+	"hongze/hongze_yb/models/tables/chart_edb_mapping"
+	"hongze/hongze_yb/models/tables/chart_info_correlation"
+	"hongze/hongze_yb/models/tables/edb_data"
+	"hongze/hongze_yb/services/alarm_msg"
+	"hongze/hongze_yb/services/chart"
+	"hongze/hongze_yb/utils"
+	"math"
+	"time"
+)
+
+// HandleDataByLinearRegression 线性方程插值法补全数据
+func HandleDataByLinearRegression(originList []*edb_data.EdbDataList, handleDataMap map[string]float64) (newList []*edb_data.EdbDataList, err error) {
+	if len(originList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *edb_data.EdbDataList
+	for _, v := range originList {
+		handleDataMap[v.DataTime] = v.Value
+
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			newList = append(newList, &edb_data.EdbDataList{
+				DataTime: v.DataTime,
+				Value:    v.Value,
+			})
+			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
+			newList = append(newList, &edb_data.EdbDataList{
+				DataTime: v.DataTime,
+				Value:    v.Value,
+			})
+			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 = fmt.Errorf("线性方程公式生成失败")
+				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).Round(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+				newList = append(newList, &edb_data.EdbDataList{
+					DataTime: tmpDataTime.Format(utils.FormatDate),
+					Value:    val,
+				})
+			}
+		}
+
+		startEdbInfoData = v
+	}
+	return
+}
+
+// MoveDataDaysToNewDataList 平移指标数据生成新的数据序列
+func MoveDataDaysToNewDataList(dataList []*edb_data.EdbDataList, moveDay int) (newDataList []edb_data.EdbDataList, dateDataMap map[string]float64) {
+	dateMap := make(map[time.Time]float64)
+	var minDate, maxDate time.Time
+	dateDataMap = make(map[string]float64)
+
+	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 {
+			//找不到数据,那么就用前面的数据吧
+			if len(newDataList)-1 < 0 {
+				tmpValue = 0
+			} else {
+				tmpValue = newDataList[len(newDataList)-1].Value
+			}
+		}
+		tmpData := edb_data.EdbDataList{
+			DataTime: currDate.Format(utils.FormatDate),
+			Value:    tmpValue,
+		}
+		dateDataMap[tmpData.DataTime] = tmpData.Value
+		newDataList = append(newDataList, tmpData)
+	}
+	return
+}
+
+// GetChartEdbInfoFormat 相关性图表-获取指标信息
+func GetChartEdbInfoFormat(chartInfoId int, edbInfoMappingA, edbInfoMappingB *chart_edb_mapping.ChartEdbInfoMapping) (edbList []*chart_edb_mapping.ChartEdbInfoMappingList, err error) {
+	edbList = make([]*chart_edb_mapping.ChartEdbInfoMappingList, 0)
+	if edbInfoMappingA == nil || edbInfoMappingB == nil {
+		err = fmt.Errorf("指标信息有误")
+		return
+	}
+
+	edbInfoMappingA.FrequencyEn = chart.GetFrequencyEn(edbInfoMappingA.Frequency)
+	if edbInfoMappingA.Unit == `无` {
+		edbInfoMappingA.Unit = ``
+	}
+	if edbInfoMappingB.Unit == `无` {
+		edbInfoMappingB.Unit = ``
+	}
+	if chartInfoId <= 0 {
+		edbInfoMappingA.IsAxis = 1
+		edbInfoMappingA.LeadValue = 0
+		edbInfoMappingA.LeadUnit = ""
+		edbInfoMappingA.ChartEdbMappingId = 0
+		edbInfoMappingA.ChartInfoId = 0
+		edbInfoMappingA.IsOrder = false
+		edbInfoMappingA.EdbInfoType = 1
+		edbInfoMappingA.ChartStyle = ""
+		edbInfoMappingA.ChartColor = ""
+		edbInfoMappingA.ChartWidth = 0
+
+		edbInfoMappingB.IsAxis = 1
+		edbInfoMappingB.LeadValue = 0
+		edbInfoMappingB.LeadUnit = ""
+		edbInfoMappingB.ChartEdbMappingId = 0
+		edbInfoMappingB.ChartInfoId = 0
+		edbInfoMappingB.IsOrder = false
+		edbInfoMappingB.EdbInfoType = 1
+		edbInfoMappingB.ChartStyle = ""
+		edbInfoMappingB.ChartColor = ""
+		edbInfoMappingB.ChartWidth = 0
+	} else {
+		edbInfoMappingA.LeadUnitEn = chart.GetLeadUnitEn(edbInfoMappingA.LeadUnit)
+		edbInfoMappingB.LeadUnitEn = chart.GetLeadUnitEn(edbInfoMappingB.LeadUnit)
+	}
+	aList := new(chart_edb_mapping.ChartEdbInfoMappingList)
+	aList.ChartEdbInfoMapping = *edbInfoMappingA
+	bList := new(chart_edb_mapping.ChartEdbInfoMappingList)
+	bList.ChartEdbInfoMapping = *edbInfoMappingB
+	edbList = append(edbList, aList, bList)
+	return
+}
+
+// GetChartDataByEdbInfo 相关性图表-根据指标信息获取x轴和y轴
+func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *chart_edb_mapping.ChartEdbInfoMapping, leadValue int, leadUnit, startDate, endDate string) (xEdbIdValue []int, yDataList []chart_info.YData, err error) {
+	xData := make([]int, 0)
+	yData := make([]float64, 0)
+	if leadValue == 0 {
+		xData = append(xData, 0)
+	}
+	if leadValue > 0 {
+		leadMin := 0 - leadValue
+		xLen := 2*leadValue + 1
+		for i := 0; i < xLen; i++ {
+			n := leadMin + i
+			xData = append(xData, n)
+		}
+	}
+
+	// 若不同频, 则需要将低频指标变为高频
+	frequencyIntMap := map[string]int{
+		"日度": 1, "周度": 2, "旬度": 3, "月度": 4, "季度": 5, "年度": 6,
+	}
+	baseEdbInfo := edbInfoMappingA
+	changeEdbInfo := edbInfoMappingB
+	if edbInfoMappingA.Frequency != edbInfoMappingB.Frequency {
+		if frequencyIntMap[edbInfoMappingA.Frequency] >= frequencyIntMap[edbInfoMappingB.Frequency] {
+			baseEdbInfo = edbInfoMappingB
+			changeEdbInfo = edbInfoMappingA
+		}
+	}
+
+	// 获取时间基准指标在时间区间内的值
+	baseDataList := make([]*edb_data.EdbDataList, 0)
+	switch baseEdbInfo.EdbInfoCategoryType {
+	case 0:
+		baseDataList, err = edb_data.GetEdbDataList(baseEdbInfo.Source, baseEdbInfo.EdbInfoId, startDate, endDate)
+	case 1:
+		_, baseDataList, _, _, err, _ = chart.GetPredictDataListByPredictEdbInfoId(baseEdbInfo.EdbInfoId, startDate, endDate, false)
+	default:
+		err = errors.New("指标base类型异常")
+		return
+	}
+
+	// 获取变频指标所有日期的值, 插值法完善数据
+	changeDataList := make([]*edb_data.EdbDataList, 0)
+	switch changeEdbInfo.EdbInfoCategoryType {
+	case 0:
+		changeDataList, err = edb_data.GetEdbDataList(changeEdbInfo.Source, changeEdbInfo.EdbInfoId, "", "")
+	case 1:
+		_, changeDataList, _, _, err, _ = chart.GetPredictDataListByPredictEdbInfoId(changeEdbInfo.EdbInfoId, "", "", false)
+	default:
+		err = errors.New("指标change类型异常")
+		return
+	}
+	changeDataMap := make(map[string]float64)
+	newChangeDataList, e := HandleDataByLinearRegression(changeDataList, changeDataMap)
+	if e != nil {
+		err = fmt.Errorf("获取变频指标插值法Map失败, Err: %s", e.Error())
+		return
+	}
+
+	// 计算不领先也不滞后时的相关系数
+	baseCalculateData := make([]float64, 0)
+	baseDataTimeArr := make([]string, 0)
+	for i := range baseDataList {
+		baseDataTimeArr = append(baseDataTimeArr, baseDataList[i].DataTime)
+		baseCalculateData = append(baseCalculateData, baseDataList[i].Value)
+	}
+	zeroCalculateData := make([]float64, 0)
+	for i := range baseDataTimeArr {
+		zeroCalculateData = append(zeroCalculateData, changeDataMap[baseDataTimeArr[i]])
+	}
+	if len(baseCalculateData) != len(zeroCalculateData) {
+		err = fmt.Errorf("相关系数两组序列元素数不一致, %d-%d", len(baseCalculateData), len(zeroCalculateData))
+		return
+	}
+	zeroRatio := utils.CalculateCorrelationByIntArr(baseCalculateData, zeroCalculateData)
+	if leadValue == 0 {
+		yData = append(yData, zeroRatio)
+	}
+
+	// 计算领先/滞后N期
+	if leadValue > 0 {
+		// 平移变频指标领先/滞后的日期(单位天)
+		frequencyDaysMap := map[string]float64{
+			"天": 1, "周": 7, "月": 30, "季": 90, "年": 365,
+		}
+		moveUnitDays := frequencyDaysMap[leadUnit]
+
+		// 此处需注意, 图表代表的意思是A领先/滞后B指标的相关系数, 只是时间序列以高频的为基准
+		// 所以需要判断高频指标是A还是B
+		// 举例: 当高频指标为A时, 即B为变频, newChangeDataList为B的值, A领先B三天则mDays应为+3, 取B滞后三天的值与基准日期的时间相比较
+		for i := range xData {
+			if xData[i] == 0 {
+				yData = append(yData, zeroRatio)
+				continue
+			}
+			yCalculateData := make([]float64, 0)
+
+			// 判断是向前平移还是向后平移
+			mDays := 0
+			if baseEdbInfo.EdbInfoId == edbInfoMappingA.EdbInfoId {
+				mDays = int(moveUnitDays) * -xData[i]
+			} else {
+				mDays = int(moveUnitDays) * xData[i]
+			}
+			_, dMap := MoveDataDaysToNewDataList(newChangeDataList, mDays)
+
+			// 取出对应的基准日期的值
+			for i2 := range baseDataTimeArr {
+				yCalculateData = append(yCalculateData, dMap[baseDataTimeArr[i2]])
+			}
+			if len(baseCalculateData) != len(yCalculateData) {
+				err = fmt.Errorf("领先滞后相关系数两组序列元素数不一致, %d-%d", len(baseCalculateData), len(yCalculateData))
+				return
+			}
+
+			// 公式计算出领先/滞后频度对应点的相关性系数
+			ratio := utils.CalculateCorrelationByIntArr(baseCalculateData, yCalculateData)
+			yData = append(yData, ratio)
+		}
+	}
+
+	xEdbIdValue = xData
+	yDataList = make([]chart_info.YData, 0)
+	yDate := "0000-00-00"
+	yDataList = append(yDataList, chart_info.YData{
+		Date:  yDate,
+		Value: yData,
+	})
+	return
+}
+
+// ChartInfoRefresh 图表刷新
+func ChartInfoRefresh(chartInfoId int) (err error) {
+	var errMsg string
+	defer func() {
+		if err != nil {
+			//fmt.Println(err.Error())
+			go alarm_msg.SendAlarmMsg("CorrelationChartInfoRefresh: "+errMsg, 3)
+		}
+	}()
+	correlationChart := new(chart_info_correlation.ChartInfoCorrelation)
+	if err = correlationChart.GetItemById(chartInfoId); err != nil {
+		errMsg = "获取相关性图表失败, Err: " + err.Error()
+		return
+	}
+
+	// 批量刷新ETA指标
+	err, errMsg = chart.EdbInfoRefreshAllFromBase([]int{correlationChart.EdbInfoIdFirst, correlationChart.EdbInfoIdSecond}, false)
+	if err != nil {
+		return
+	}
+
+	// 重新生成数据并更新
+	edbInfoMappingA, err := chart_edb_mapping.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdFirst)
+	if err != nil {
+		errMsg = "获取相关性图表, A指标mapping信息失败, Err:" + err.Error()
+		return
+	}
+	edbInfoMappingB, err := chart_edb_mapping.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
+	if err != nil {
+		errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + err.Error()
+		return
+	}
+	periodData, correlationData, err := GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
+	if err != nil {
+		errMsg = "获取相关性图表, 图表计算值失败, Err:" + err.Error()
+		return
+	}
+	periodDataByte, err := json.Marshal(periodData)
+	if err != nil {
+		errMsg = "相关性图表, X轴信息有误, Err:" + err.Error()
+		return
+	}
+	correlationDataByte, err := json.Marshal(correlationData[0].Value)
+	if err != nil {
+		errMsg = "相关性图表, Y轴信息有误, Err:" + err.Error()
+		return
+	}
+	correlationChart.PeriodData = string(periodDataByte)
+	correlationChart.CorrelationData = string(correlationDataByte)
+	correlationChart.ModifyTime = time.Now().Local()
+	correlationUpdateCols := []string{"PeriodData", "CorrelationData", "ModifyTime"}
+	if err = correlationChart.Update(correlationUpdateCols); err != nil {
+		errMsg = "更新相关性图表失败, Err:" + err.Error()
+		return
+	}
+	return
+}

+ 50 - 0
utils/calculate.go

@@ -1,5 +1,7 @@
 package utils
 
+import "math"
+
 // Series is a container for a series of data
 type Series []Coordinate
 
@@ -43,3 +45,51 @@ func GetLinearResult(s []Coordinate) (gradient, intercept float64) {
 
 	return
 }
+
+// CalculateCorrelationByIntArr 相关性计算
+// 计算步骤
+// 1.分别计算两个序列的平均值Mx和My
+// 2.分别计算两个序列的标准偏差SDx和SDy	=> √{1/(n-1)*SUM[(Xi-Mx)²]}
+// 3.计算相关系数	=> SUM[(Xi-Mx)*(Yi-My)]/[(N-1)(SDx*SDy)]
+func CalculateCorrelationByIntArr(xArr, yArr []float64) (ratio float64) {
+	// 序列元素数要一致
+	xLen := float64(len(xArr))
+	yLen := float64(len(yArr))
+	if xLen == 0 || xLen != yLen {
+		return
+	}
+
+	// 计算Mx和My
+	var Xa, Ya float64
+	for i := range xArr {
+		Xa += xArr[i]
+	}
+	Mx := Xa / xLen
+	for i := range yArr {
+		Ya += yArr[i]
+	}
+	My := Ya / yLen
+
+	// 计算标准偏差SDx和SDy
+	var Xb, Yb, SDx, SDy float64
+	for i := range xArr {
+		Xb += (xArr[i] - Mx) * (xArr[i] - Mx)
+	}
+	SDx = math.Sqrt(1 / (xLen - 1) * Xb)
+	for i := range yArr {
+		Yb += (yArr[i] - My) * (yArr[i] - My)
+	}
+	SDy = math.Sqrt(1 / (yLen - 1) * Yb)
+
+	// 计算相关系数
+	var Nume, Deno float64
+	for i := 0; i < int(xLen); i++ {
+		Nume += (xArr[i] - Mx) * (yArr[i] - My)
+	}
+	Deno = (xLen - 1) * (SDx * SDy)
+	ratio = Nume / Deno
+	if math.IsNaN(ratio) {
+		ratio = 0
+	}
+	return
+}

+ 1 - 0
utils/constants.go

@@ -247,4 +247,5 @@ const ALIYUN_OSS_HOST = "https://hzstatic.hzinsights.com"
 const (
 	CHART_SOURCE_DEFAULT     = 1
 	CHART_SOURCE_FUTURE_GOOD = 2
+	CHART_SOURCE_CORRELATION = 3 // 相关性图表
 )