gmy 4 ヶ月 前
コミット
0f76739b4e

+ 38 - 1
controllers/residual_analysis/residual_analysis.go

@@ -42,7 +42,7 @@ func (this *ResidualAnalysisController) ResidualAnalysisPreview() {
 		return
 	}
 
-	err = residual_analysis_service.ResidualAnalysisPreview(req)
+	resp, err := residual_analysis_service.ResidualAnalysisPreview(req)
 	if err != nil {
 		br.Ret = 408
 		br.Msg = "获取失败"
@@ -55,3 +55,40 @@ func (this *ResidualAnalysisController) ResidualAnalysisPreview() {
 	br.Msg = "获取成功"
 	br.Data = resp
 }
+
+// ContrastPreview
+// @Title 对比指标预览
+// @Description 对比指标预览
+// @Param
+// @Success
+// @router /contrast/preview [get]
+func (this *ResidualAnalysisController) ContrastPreview() {
+	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
+	}
+
+	IndexCode := this.GetString("IndexCode")
+	if IndexCode == "" {
+		br.Ret = 408
+		br.Msg = "IndexCode不能为空"
+		br.ErrMsg = "IndexCode不能为空"
+		return
+	}
+
+	residual_analysis_service.ContrastPreview(IndexCode)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 1 - 1
models/residual_analysis_model/calculate_residual_analysis_config.go

@@ -24,7 +24,7 @@ type ResidualAnalysisReq struct {
 	StartDate        string  `description:"自定义开始日期"`
 	EndDate          string  `description:"自定义结束日期"`
 	IsOrder          bool    `description:"true:正序,false:逆序"`
-	IndexType        int     `description:"1-标准指标 1-领先指标"`
+	IndexType        int     `description:"1-标准指标 2-领先指标"`
 	LeadValue        int     `description:"领先值"`
 	LeadFrequency    string  `description:"领先频度"`
 	LeftIndexMin     float64 `description:"指标A左侧下限"`

+ 115 - 11
services/residual_analysis_service/residual_analysis_service.go

@@ -96,29 +96,104 @@ func ResidualAnalysisPreview(req residual_analysis_model.ResidualAnalysisReq) (r
 	}
 
 	// 残差图表信息
-	fillResidualChartInfo(req, edbInfoMappingA, edbInfoMappingB, mappingEdbList, indexADataMap)
+	ResidualEdbList, err := fillResidualChartInfo(edbInfoMappingA, edbInfoMappingB, mappingEdbList)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisResp{}, err
+	}
+
+	baseChartInfo.ChartName = edbInfoMappingA.EdbName + "与" + edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName
+
+	resp.MappingChartData = residual_analysis_model.ChartResp{
+		ChartInfo:   baseChartInfo,
+		EdbInfoList: ResidualEdbList,
+	}
 
 	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) {
+func fillResidualChartInfo(edbInfoMappingA *data_manage.ChartEdbInfoMapping, edbInfoMappingB *data_manage.ChartEdbInfoMapping, mappingEdbList []*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping) ([]*residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
 	// 计算公式 映射残差 = 因变量指标 - 映射指标
+	var edbInfoA, edbInfoB *residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+	if mappingEdbList[0].EdbInfoId == edbInfoMappingA.EdbInfoId {
+		edbInfoA = mappingEdbList[0]
+		edbInfoB = mappingEdbList[1]
+	} else {
+		edbInfoA = mappingEdbList[1]
+		edbInfoB = mappingEdbList[0]
+	}
+	dataAList, ok := edbInfoA.DataList.([]data_manage.EdbDataList)
+	if !ok {
+		return nil, fmt.Errorf("数据类型转换失败", nil)
+	}
+	edbData := dataAList
+	dataBList, ok := edbInfoB.DataList.([]data_manage.EdbDataList)
+	if !ok {
+		return nil, fmt.Errorf("数据类型转换失败", nil)
+	}
+	var indexDataBMap map[string]data_manage.EdbDataList
+	for _, data := range dataBList {
+		indexDataBMap[data.DataTime] = data
+	}
+	var valueB float64
+	for _, indexData := range edbData {
+		if dataB, ok := indexDataBMap[indexData.DataTime]; ok {
+			valueB = dataB.Value
+		} else {
+			valueB = 0
+		}
+		indexData.Value = indexData.Value - valueB
+	}
 	for _, mapping := range mappingEdbList {
-		if mapping.EdbInfoId == req.EdbInfoIdB {
-			valueA := indexADataMap[mapping.data]
+		if mapping.EdbInfoId != edbInfoMappingA.EdbInfoId {
+			mapping.DataList = edbData
+			mapping.EdbName = edbInfoMappingA.EdbName + "映射残差/" + edbInfoMappingB.EdbName
 		}
 	}
+
+	return mappingEdbList, nil
 }
 
 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
+	// 正序:a=(L2-L1)/(R2-R1)	b=L2-R2*a
+	// 逆序:a=(L2-L1)/(R1-R2)	b=L2-R1*a
+	// L2:左轴下限 R2:右轴上限 L1:左轴上限 R1:右轴下限
+	var a, b float64
+	if req.IsOrder {
+		a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMax - req.RightIndexMin)
+		b = req.LeftIndexMax - req.RightIndexMax*a
+	} else {
+		a = (req.LeftIndexMax - req.LeftIndexMin) / (req.RightIndexMin - req.RightIndexMax)
+		b = req.LeftIndexMax - req.RightIndexMin*a
+	}
+
 	dataList, ok := edbInfoMappingB.DataList.([]data_manage.EdbDataList)
 	if !ok {
 		return nil, fmt.Errorf("数据类型转换失败", nil)
 	}
+
+	// 领先指标 dataList进行数据处理
+	if req.IndexType == 2 {
+		if req.LeadValue < 0 {
+			return nil, fmt.Errorf("领先值不能小于0", nil)
+		} else if req.LeadValue > 0 {
+			for _, indexData := range dataList {
+				switch req.LeadFrequency {
+				case "天":
+					indexData.DataTime = utils.GetNextDayN(indexData.DataTime, req.LeadValue)
+				case "周":
+					indexData.DataTime = utils.GetNextDayN(indexData.DataTime, req.LeadValue*7)
+				case "月":
+					indexData.DataTime = utils.TimeToString(utils.AddDate(utils.StringToTime(indexData.DataTime), 0, req.LeadValue), utils.YearMonthDay)
+				case "季":
+					indexData.DataTime = utils.TimeToString(utils.AddDate(utils.StringToTime(indexData.DataTime), 0, req.LeadValue*3), utils.YearMonthDay)
+				case "年":
+					indexData.DataTime = utils.TimeToString(utils.AddDate(utils.StringToTime(indexData.DataTime), req.LeadValue, 0), utils.YearMonthDay)
+				}
+			}
+		}
+	}
+
 	for index, indexData := range dataList {
 		// 计算映射值
 		indexData.Value = a*indexData.Value + b
@@ -136,7 +211,6 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 		}
 	}
 	// 根据指标A的时间key,在B的映射指标中筛选出对应的值
-
 	var dataBList []data_manage.EdbDataList
 	for _, indexData := range dataList {
 		if _, ok := indexADataMap[indexData.DataTime]; ok {
@@ -146,9 +220,10 @@ func fillMappingChartInfo(req residual_analysis_model.ResidualAnalysisReq, edbIn
 
 	mappingEdbList := originalEdbList
 	for _, mapping := range mappingEdbList {
-		if mapping.EdbInfoId == req.EdbInfoIdA {
-			mappingEdbList = append(mappingEdbList, mapping)
-		} else {
+		if mapping.EdbInfoId == req.EdbInfoIdB {
+			mapping.EdbInfoId = 0
+			mapping.EdbCode = ""
+			mapping.EdbName = edbInfoMappingB.EdbName + "映射" + edbInfoMappingA.EdbName
 			mapping.DataList = dataList
 		}
 	}
@@ -196,3 +271,32 @@ func fillOriginalChart(req residual_analysis_model.ResidualAnalysisReq, mappingL
 	}
 	return OriginalEdbList, nil
 }
+
+func ContrastPreview(indexCode string) (residual_analysis_model.ResidualAnalysisChartEdbInfoMapping, error) {
+	var condition string
+	var pars []interface{}
+
+	if indexCode != "" {
+		condition += " and edb_code=?"
+		pars = append(pars, indexCode)
+	}
+
+	edbInfo, err := data_manage.GetEdbInfoByCondition(condition, pars)
+	if err != nil {
+		return residual_analysis_model.ResidualAnalysisChartEdbInfoMapping{}, err
+	}
+	if edbInfo == nil {
+		return residual_analysis_model.ResidualAnalysisChartEdbInfoMapping{}, fmt.Errorf("指标不存在")
+	}
+
+	dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, "", "")
+
+	var resp residual_analysis_model.ResidualAnalysisChartEdbInfoMapping
+	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.SourceName = edbInfo.SourceName
+	resp.EdbName = edbInfo.EdbName
+	resp.Unit = edbInfo.Unit
+	resp.Frequency = edbInfo.Frequency
+	resp.DataList = dataList
+	return resp, nil
+}

+ 91 - 0
utils/date_util.go

@@ -5,6 +5,16 @@ import (
 	"time"
 )
 
+// 定义时间格式常量
+const (
+	YearMonthDay     = "2006-01-02"                     // yyyy-MM-dd
+	YearMonthDayTime = "2006-01-02 15:04:05"            // yyyy-MM-dd HH:mm:ss
+	MonthDay         = "01-02"                          // MM-dd
+	DayMonthYear     = "02-01-2006"                     // dd-MM-yyyy
+	YearMonth        = "2006-01"                        // yyyy-MM
+	FullDate         = "Monday, 02-Jan-06 15:04:05 PST" // 完整日期:例如:Monday, 02-Jan-06 15:04:05 PST
+)
+
 // GetPreYear 获取当前时间 前n年的时间
 func GetPreYear(n int) string {
 	now := time.Now()
@@ -27,3 +37,84 @@ func GetNextDay(date string) string {
 	nextDay := t.AddDate(0, 0, 1)
 	return nextDay.Format("2006-01-02")
 }
+
+// GetNextDayN 获取 yyyy-MM-dd 类型的时间的下n天
+func GetNextDayN(date string, n int) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, n)
+	return nextDay.Format("2006-01-02")
+}
+
+var daysOfMonth = [...]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+
+// AddDate 解决 Go time 包 AddDate() 添加年份/月份溢出到下一个月的问题。
+// 例如:
+//
+//	2024-02-29 AddDate(1, 0, 0) 期望结果: 2025-02-28
+//	2024-08-31 AddDate(0, 1, 1) 期望结果: 2024-09-30
+func AddDate(t time.Time, years, months int) time.Time {
+	month := t.Month()
+
+	// 规范年份和月份
+	years, months = norm(years, months, 12)
+
+	// 计算目标月份
+	targetMonth := int(month) + months
+	if targetMonth <= 0 {
+		// 处理负值月份
+		targetMonth += 12 * ((-targetMonth)/12 + 1)
+	}
+	// 取余计算目标月份
+	targetMonth = (targetMonth-1)%12 + 1
+
+	// 计算目标年份
+	targetYear := t.Year() + years + (int(month)+months)/12
+
+	// 计算目标月份最大天数
+	maxDayOfTargetMonth := daysOfMonth[targetMonth-1]
+	if isLeap(targetYear) && targetMonth == 2 {
+		maxDayOfTargetMonth++ // 闰年2月多一天
+	}
+
+	// 计算目标日期
+	targetDay := t.Day()
+	if targetDay > maxDayOfTargetMonth {
+		// 如果目标日期超出该月的天数,设置为该月的最后一天
+		targetDay = maxDayOfTargetMonth
+	}
+
+	// 返回新的日期
+	return time.Date(targetYear, time.Month(targetMonth), targetDay, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+}
+
+// norm 规范化年份和月份
+func norm(hi, lo, base int) (nhi, nlo int) {
+	if lo < 0 {
+		n := (-lo-1)/base + 1
+		hi -= n
+		lo += n * base
+	}
+	if lo >= base {
+		n := lo / base
+		hi += n
+		lo -= n * base
+	}
+	return hi, lo
+}
+
+// isLeap 判断是否为闰年
+func isLeap(year int) bool {
+	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
+
+// StringToTime string 类型时间 转换为 time.Time 类型
+func StringToTime(date string) time.Time {
+	t, _ := time.Parse("2006-01-02", date)
+	return t
+}
+
+// TimeToString time.Time 类型时间 转换为 string 类型
+func TimeToString(t time.Time, format string) string {
+	formattedTime := t.Format(format)
+	return formattedTime
+}