浏览代码

残差分析-代码开发

gmy 6 月之前
父节点
当前提交
9b8cdf3473

+ 57 - 0
controllers/residual_analysis/residual_analysis.go

@@ -0,0 +1,57 @@
+package residual_analysis
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/residual_analysis_model"
+	"eta/eta_api/services/residual_analysis_service"
+)
+
+// ResidualAnalysisController 残差分析
+type ResidualAnalysisController struct {
+	controllers.BaseAuthController
+}
+
+// ResidualAnalysisPreview
+// @Title 残差分析预览
+// @Description 残差分析预览
+// @Param
+// @Success
+// @router /residual/analysis/preview [post]
+func (this *ResidualAnalysisController) ResidualAnalysisPreview() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req residual_analysis_model.ResidualAnalysisReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	err = residual_analysis_service.ResidualAnalysisPreview(req)
+	if err != nil {
+		br.Ret = 408
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 106 - 0
models/residual_analysis_model/calculate_residual_analysis_config.go

@@ -0,0 +1,106 @@
+package residual_analysis_model
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type CalculateResidualAnalysisConfig struct {
+	CalculateResidualAnalysisConfigId int    `orm:"column(calculate_residual_analysis_config_id);pk;auto" description:"自增id"`
+	Config                            string `orm:"column(config)" description:"计算参数配置"`
+	SysUserId                         int    `orm:"column(sys_user_id)" description:"操作人id"`
+	CreateTime                        string `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime                        string `orm:"column(modify_time)" description:"修改时间"`
+}
+
+func init() {
+	orm.RegisterModel(new(CalculateResidualAnalysisConfig))
+}
+
+// ResidualAnalysisReq 残差分析预览请求
+type ResidualAnalysisReq struct {
+	EdbInfoIdA       int     `description:"指标A"`
+	EdbInfoIdB       int     `description:"指标B"`
+	DateType         int     `description:"时间类型 0-自定义时间 1-至今 n-枚举时间(近n年)"`
+	StartDate        string  `description:"自定义开始日期"`
+	EndDate          string  `description:"自定义结束日期"`
+	IsOrder          bool    `description:"true:正序,false:逆序"`
+	IndexType        int     `description:"1-标准指标 1-领先指标"`
+	LeadValue        int     `description:"领先值"`
+	LeadFrequency    string  `description:"领先频度"`
+	LeftIndexMin     float64 `description:"指标A左侧下限"`
+	LeftIndexMax     float64 `description:"指标A左侧上限"`
+	RightIndexMin    float64 `description:"指标B右侧下限"`
+	RightIndexMax    float64 `description:"指标B右侧上限"`
+	ResidualIndexMin float64 `description:"残差指标下限"`
+	ResidualIndexMax float64 `description:"残差指标上限"`
+	ContrastIndexMin float64 `description:"对比指标下限"`
+	ContrastIndexMax float64 `description:"对比指标上限"`
+}
+
+// ResidualAnalysisResp 残差分析预览响应
+type ResidualAnalysisResp struct {
+	OriginalChartData ChartResp `description:"原始图数据"`
+	MappingChartData  ChartResp `description:"映射图数据"`
+	ResidualChartData ChartResp `description:"残差图数据"`
+}
+
+type ChartResp struct {
+	ChartInfo   *ResidualAnalysisChartInfo
+	EdbInfoList []*ResidualAnalysisChartEdbInfoMapping
+}
+
+type ResidualAnalysisChartInfo struct {
+	ChartName     string  `description:"来源名称"`
+	ChartNameEn   string  `description:"英文图表名称"`
+	Unit          string  `description:"中文单位名称"`
+	UnitEn        string  `description:"英文单位名称"`
+	UniqueCode    string  `description:"图表唯一编码"`
+	DateType      int     `description:"时间类型 0-自定义时间 1-至今 n-枚举时间(近n年)"`
+	StartDate     string  `description:"自定义开始日期"`
+	EndDate       string  `description:"自定义结束日期"`
+	ChartType     int     `description:"生成样式:1:曲线图,2:季节性图"`
+	ChartWidth    float64 `description:"线条大小"`
+	Calendar      string  `description:"公历/农历"`
+	Disabled      int     `description:"是否禁用,0:启用,1:禁用,默认:0"`
+	Source        int     `description:"1:ETA图库;2:商品价格曲线;3:相关性图表"`
+	ChartSource   string  `description:"图表来源str"`
+	ChartSourceEn string  `description:"图表来源(英文)"`
+	SourcesFrom   string  `description:"图表来源"`
+	Instructions  string  `description:"图表说明"`
+}
+
+type ResidualAnalysisChartEdbInfoMapping struct {
+	EdbInfoId           int     `description:"指标id"`
+	SourceName          string  `description:"来源名称"`
+	Source              int     `description:"来源id"`
+	EdbCode             string  `description:"指标编码"`
+	EdbName             string  `description:"指标名称"`
+	EdbNameEn           string  `description:"英文指标名称"`
+	EdbType             int     `description:"指标类型:1:基础指标,2:计算指标"`
+	Frequency           string  `description:"频率"`
+	FrequencyEn         string  `description:"英文频率"`
+	Unit                string  `description:"单位"`
+	UnitEn              string  `description:"英文单位"`
+	StartDate           string  `description:"起始日期"`
+	EndDate             string  `description:"终止日期"`
+	ModifyTime          string  `description:"指标最后更新时间"`
+	MaxData             float64 `description:"上限"`
+	MinData             float64 `description:"下限"`
+	IsOrder             bool    `description:"true:正序,false:逆序"`
+	IsAxis              int     `description:"1:左轴,0:右轴"`
+	EdbInfoType         int     `description:"1:标准指标,0:领先指标"`
+	EdbInfoCategoryType int     `description:"0:普通指标,1:预测指标"`
+	LeadValue           int     `description:"领先值"`
+	LeadUnit            string  `description:"领先单位"`
+	LeadUnitEn          string  `description:"领先英文单位"`
+	ChartStyle          string  `description:"图表类型"`
+	ChartColor          string  `description:"颜色"`
+	PredictChartColor   string  `description:"预测数据的颜色"`
+	ChartWidth          float64 `description:"线条大小"`
+	ChartType           int     `description:"生成样式:1:曲线图,2:季节性图,3:面积图,4:柱状图,5:散点图,6:组合图,7:柱方图,8:商品价格曲线图,9:相关性图"`
+	LatestDate          string  `description:"数据最新日期"`
+	LatestValue         float64 `description:"数据最新值"`
+	MinValue            float64 `json:"-" description:"最小值"`
+	MaxValue            float64 `json:"-" description:"最大值"`
+	DataList            interface{}
+}

+ 16 - 0
models/residual_analysis_model/calculate_residual_analysis_config_mapping.go

@@ -0,0 +1,16 @@
+package residual_analysis_model
+
+import "github.com/beego/beego/v2/client/orm"
+
+type CalculateResidualAnalysisConfigMapping struct {
+	CalculateResidualAnalysisConfigMappingId int    `orm:"column(calculate_residual_analysis_config_mapping_id);pk;auto" description:"自增id"`
+	CalculateResidualAnalysisConfigId        int    `orm:"column(calculate_residual_analysis_config_id)" description:"残差分析配置id"`
+	EdbInfoId                                int    `orm:"column(edb_info_id)" description:"指标id"`
+	ResidualType                             int    `orm:"column(residual_type)" description:"残差类型: 1-映射残差 2-拟合残差"`
+	CreateTime                               string `orm:"column(create_time)" description:"创建时间"`
+	ModifyTime                               string `orm:"column(modify_time)" description:"修改时间"`
+}
+
+func init() {
+	orm.RegisterModel(new(CalculateResidualAnalysisConfigMapping))
+}

+ 16 - 0
models/residual_analysis_model/edb_data_residual_analysis.go

@@ -0,0 +1,16 @@
+package residual_analysis_model
+
+import "github.com/beego/beego/v2/client/orm"
+
+type ResidualAnalysisData struct {
+	EdbDataId     int     `orm:"column(edb_data_id);pk;auto" description:"自增id"`
+	EdbInfoId     int     `orm:"column(edb_info_id)" description:"指标id"`
+	EdbCode       string  `orm:"column(edb_code)" description:"指标编码"`
+	DataTime      string  `orm:"column(data_time)" description:"数据日期"`
+	Value         float64 `orm:"column(value)" description:"数据值"`
+	DataTimeStamp int64   `orm:"column(data_timestamp)"`
+}
+
+func init() {
+	orm.RegisterModel(new(ResidualAnalysisData))
+}

+ 198 - 0
services/residual_analysis_service/residual_analysis_service.go

@@ -0,0 +1,198 @@
+package residual_analysis_service
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/residual_analysis_model"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (residual_analysis_model.ResidualAnalysisResp, error) {
+
+	if req.DateType < 0 {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("时间类型错误", nil)
+	}
+
+	mappingList, err := data_manage.GetChartEdbMappingListByEdbInfoIdList([]int{req.EdbInfoIdA, req.EdbInfoIdB})
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("获取图表,指标信息失败,Err:%s", err.Error())
+	}
+
+	var edbInfoMappingA, edbInfoMappingB *data_manage.ChartEdbInfoMapping
+	for _, v := range mappingList {
+		if v.EdbInfoId == req.EdbInfoIdA {
+			edbInfoMappingA = v
+		}
+		if v.EdbInfoId == req.EdbInfoIdB {
+			edbInfoMappingB = v
+		}
+	}
+	if edbInfoMappingA == nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标A不存在", nil)
+	}
+	if edbInfoMappingB == nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("指标B不存在", nil)
+	}
+
+	// 时间处理
+	var startDate, endDate string
+	switch req.DateType {
+	case 0:
+		startDate = req.StartDate
+		endDate = req.EndDate
+	case 1:
+		startDate = req.StartDate
+		endDate = ""
+	default:
+		startDate = utils.GetPreYear(req.DateType)
+		endDate = ""
+	}
+
+	resp := residual_analysis_model.ResidualAnalysisResp{}
+
+	// 图表基础信息
+	baseChartInfo := new(residual_analysis_model.ResidualAnalysisChartInfo)
+	baseChartInfo.Calendar = `公历`
+	baseChartInfo.Source = utils.CHART_SOURCE_DEFAULT
+	baseChartInfo.DateType = req.DateType
+	baseChartInfo.StartDate = startDate
+	baseChartInfo.EndDate = endDate
+	baseChartInfo.ChartType = utils.CHART_TYPE_CURVE
+
+	// 原始图表信息
+	var OriginalEdbList []*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+
+	OriginalEdbList, err = fillOriginalChart(req, mappingList, startDate, endDate, edbInfoMappingA, edbInfoMappingB, OriginalEdbList)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, err
+	}
+	baseChartInfo.ChartName = edbInfoMappingA.EdbName + "与" + edbInfoMappingB.EdbName
+
+	resp.OriginalChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   baseChartInfo,
+		EdbInfoList: OriginalEdbList,
+	}
+
+	dataAList, ok := edbInfoMappingA.DataList.([]data_manage.EdbDataList)
+	if !ok {
+		return residual_analysis_model.ResidualAnalysisResp{}, fmt.Errorf("数据类型转换失败", nil)
+	}
+	indexADataMap := map[string]data_manage.EdbDataList{}
+	for _, indexData := range dataAList {
+		indexADataMap[indexData.DataTime] = indexData
+	}
+
+	// 映射图表信息
+	mappingEdbList, err := fillMappingChartInfo(req, edbInfoMappingA, edbInfoMappingB, OriginalEdbList, indexADataMap)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, err
+	}
+	baseChartInfo.ChartName = edbInfoMappingA.EdbName + "与" + edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
+
+	resp.MappingChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   baseChartInfo,
+		EdbInfoList: mappingEdbList,
+	}
+
+	// 残差图表信息
+	fillResidualChartInfo(req, edbInfoMappingA, edbInfoMappingB, mappingEdbList, indexADataMap)
+
+	return resp, nil
+}
+
+func fillResidualChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]data_manage.EdbDataList) {
+	// 计算公式 映射残差 = 因变量指标 - 映射指标
+	for _, mapping := range mappingEdbList {
+		if mapping.EdbInfoId == req.EdbInfoIdB {
+			valueA := indexADataMap[mapping.data]
+		}
+	}
+}
+
+func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, originalEdbList []*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, indexADataMap map[string]data_manage.EdbDataList) ([]*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+	// 计算公式:Y=aX+b,Y为映射后的指标,X为自变量指标
+	// 正序:a=(L2-L1)/(R2-R1)	b=L2-R2*a	逆序:a=(L2-L1)/(R1-R2)	b=L2-R1*a	L2:左轴下限 R2:右轴上限 L1:左轴上限 R1:右轴下限
+	a := (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
+	b := req.LeftIndexMax - req.RightIndexMax*a
+	dataList, ok := edbInfoMappingB.DataList.([]data_manage.EdbDataList)
+	if !ok {
+		return nil, fmt.Errorf("数据类型转换失败", nil)
+	}
+	for index, indexData := range dataList {
+		// 计算映射值
+		indexData.Value = a*indexData.Value + b
+
+		// 从最早时间开始 补充时间为自然日
+		beforeIndexData := dataList[index]
+		afterIndexData := dataList[index+1]
+		if utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+			replenishIndexData := data_manage.EdbDataList{
+				DataTime:      utils.GetNextDay(beforeIndexData.DataTime),
+				DataTimestamp: time.Now().UnixMilli(),
+				Value:         beforeIndexData.Value,
+			}
+			dataList = append(dataList, replenishIndexData)
+		}
+	}
+	// 根据指标A的时间key,在B的映射指标中筛选出对应的值
+
+	var dataBList []data_manage.EdbDataList
+	for _, indexData := range dataList {
+		if _, ok := indexADataMap[indexData.DataTime]; ok {
+			dataBList = append(dataBList, indexData)
+		}
+	}
+
+	mappingEdbList := originalEdbList
+	for _, mapping := range mappingEdbList {
+		if mapping.EdbInfoId == req.EdbInfoIdA {
+			mappingEdbList = append(mappingEdbList, mapping)
+		} else {
+			mapping.DataList = dataList
+		}
+	}
+	return mappingEdbList, nil
+}
+
+func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingList []*data_manage.ChartEdbInfoMapping, startDate string, endDate string, edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, OriginalEdbList []*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+	for _, v := range mappingList {
+		var edbInfoMapping *residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+		edbInfoMapping.EdbInfoType = 1
+		edbInfoMapping.IsOrder = false
+		edbInfoMapping.IsAxis = 1
+		edbInfoMapping.ChartColor = `#00F`
+		edbInfoMapping.ChartWidth = 3
+
+		// 获取图表中的指标数据
+		dataList, err := data_manage.GetEdbDataList(v.Source, v.SubSource, v.EdbInfoId, startDate, endDate)
+		if err != nil {
+			return nil, fmt.Errorf("获取指标数据失败,Err:%s", err.Error())
+		}
+
+		if v.EdbInfoId == req.EdbInfoIdB {
+			edbInfoMapping.LeadValue = req.LeadValue
+			edbInfoMapping.LeadUnit = req.LeadFrequency
+			edbInfoMapping.EdbInfoType = req.IndexType
+			edbInfoMapping.IsOrder = req.IsOrder
+			edbInfoMapping.IsAxis = 0
+			edbInfoMapping.ChartColor = `#F00`
+			edbInfoMapping.ChartWidth = 1
+
+			edbInfoMappingB.DataList = dataList
+		} else {
+			edbInfoMappingA.DataList = dataList
+		}
+		edbInfoMapping.EdbInfoId = v.EdbInfoId
+		edbInfoMapping.EdbName = v.EdbName
+		edbInfoMapping.EdbCode = v.EdbCode
+		edbInfoMapping.Unit = v.Unit
+		edbInfoMapping.Frequency = v.Frequency
+		edbInfoMapping.Source = v.Source
+		edbInfoMapping.SourceName = v.SourceName
+
+		edbInfoMapping.DataList = dataList
+		OriginalEdbList = append(OriginalEdbList, edbInfoMapping)
+	}
+	return OriginalEdbList, nil
+}

+ 29 - 0
utils/date_util.go

@@ -0,0 +1,29 @@
+package utils
+
+import (
+	"strconv"
+	"time"
+)
+
+// GetPreYear 获取当前时间 前n年的时间
+func GetPreYear(n int) string {
+	now := time.Now()
+	year := now.Year() - n
+	return strconv.Itoa(year)
+}
+
+// IsMoreThanOneDay 判断两个yyyy-MM-dd类型的时间,相差是否大于1天
+func IsMoreThanOneDay(startDate, endDate string) bool {
+	startTime, _ := time.Parse("2006-01-02", startDate)
+	endTime, _ := time.Parse("2006-01-02", endDate)
+	diff := endTime.Sub(startTime)
+	days := diff.Hours() / 24
+	return days > 1
+}
+
+// GetNextDay 获取 yyyy-MM-dd类型的时间的下一天
+func GetNextDay(date string) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, 1)
+	return nextDay.Format("2006-01-02")
+}