Forráskód Böngészése

add:stl趋势分解预览

zqbao 4 hónapja
szülő
commit
a4285d6896

+ 14 - 7
controllers/data_manage/stl/stl.go

@@ -17,7 +17,7 @@ type STLController struct {
 // @Title STL分解预览
 // @Description STL分解预览
 // @Success 200 {object} data_manage.BaiinfoClassify
-// @router /stl/chart_info/preview [post]
+// @router /chart_info/preview [post]
 func (c *STLController) Preview() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
@@ -35,15 +35,22 @@ func (c *STLController) Preview() {
 	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
 		br.Msg = "请求参数错误"
 		br.ErrMsg = err.Error()
-		br.Ret = 400
 		return
 	}
-	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+
+	resp, msg, err := stl.GenerateStlEdbData(req, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "预览异常"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
 		return
 	}
 
-	stl.GenerateStlEdbData(req)
-
+	br.Data = resp
+	br.Msg = "预览成功"
+	br.Ret = 200
+	br.Success = true
+	return
 }

+ 13 - 13
models/data_manage/stl/request/stl.go

@@ -1,17 +1,17 @@
 package request
 
 type STLReq struct {
-	EdbInfoId     int    `description:"指标ID"`
-	DataRangeType int    `description:"数据时间类型:1-全部时间,2-最近N年,3-区间设置,4-区间设置(至今)"`
-	StartDate     string `description:"开始日期"`
-	EndDate       string `description:"结束日期"`
-	LastNYear     int    `description:"最近N年"`
-	Period        int    `description:"数据的周期,根据频率设置"`
-	Seasonal      int    `description:"季节性成分窗口大小,一般为period+1,可以设置为大于period的正奇数"`
-	Trend         int    `description:"趋势成分窗口大小,一般为period+1,可以设置为大于period的正奇数"`
-	Fraction      int    `description:"趋势项的平滑系数,默认为0.2,区间为[0-1]"`
-	Robust        bool   `description:"是否使用稳健方法: true(使用) false(不使用)  "`
-	TrendDeg      int    `description:"分解中趋势多项式次数,默认为1,不超过5的正整数"`
-	SeasonalDeg   int    `description:"分解中季节性多项次数,默认为1,不超过5的正整数"`
-	LowPassDeg    int    `description:"分解中低通滤波器次数,默认为1,不超过5的正整数"`
+	EdbInfoId     int     `description:"指标ID"`
+	DataRangeType int     `description:"数据时间类型:1-全部时间,2-最近N年,3-区间设置,4-区间设置(至今)"`
+	StartDate     string  `description:"开始日期"`
+	EndDate       string  `description:"结束日期"`
+	LastNYear     int     `description:"最近N年"`
+	Period        int     `description:"数据的周期,根据频率设置"`
+	Seasonal      int     `description:"季节性成分窗口大小,一般为period+1,可以设置为大于period的正奇数"`
+	Trend         int     `description:"趋势成分窗口大小,一般为period+1,可以设置为大于period的正奇数"`
+	Fraction      float64 `description:"趋势项的平滑系数,默认为0.2,区间为[0-1]"`
+	Robust        bool    `description:"是否使用稳健方法: true(使用) false(不使用)  "`
+	TrendDeg      int     `description:"分解中趋势多项式次数,默认为1,不超过5的正整数"`
+	SeasonalDeg   int     `description:"分解中季节性多项次数,默认为1,不超过5的正整数"`
+	LowPassDeg    int     `description:"分解中低通滤波器次数,默认为1,不超过5的正整数"`
 }

+ 0 - 3
models/data_manage/stl/response/stl.go

@@ -11,10 +11,7 @@ type StlPreviewResp struct {
 type ChartEdbInfo struct {
 	Title        string
 	Unit         string
-	UnitEn       string
 	Frequency    string
-	FrequencyEn  string
-	DataTime     string
 	ClassifyId   string
 	ClassifyPath string
 	DataList     []*EdbData

+ 9 - 0
routers/commentsRouter.go

@@ -2302,6 +2302,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/stl:STLController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/stl:STLController"],
+        beego.ControllerComments{
+            Method: "Preview",
+            Router: `/chart_info/preview`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/supply_analysis:VarietyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/supply_analysis:VarietyController"],
         beego.ControllerComments{
             Method: "Add",

+ 6 - 0
routers/router.go

@@ -19,6 +19,7 @@ import (
 	"eta/eta_api/controllers/data_manage/line_equation"
 	"eta/eta_api/controllers/data_manage/line_feature"
 	"eta/eta_api/controllers/data_manage/range_analysis"
+	"eta/eta_api/controllers/data_manage/stl"
 	"eta/eta_api/controllers/data_manage/supply_analysis"
 	"eta/eta_api/controllers/data_source"
 	"eta/eta_api/controllers/data_stat"
@@ -397,6 +398,11 @@ func init() {
 				&document_manage.DocumentManageController{},
 			),
 		),
+		web.NSNamespace("/stl_decompose",
+			web.NSInclude(
+				&stl.STLController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 104 - 21
services/data/stl/stl.go

@@ -10,6 +10,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"strconv"
 	"strings"
 	"time"
 
@@ -23,7 +24,7 @@ const (
 	RANGE_DATE_TO_NOW
 )
 
-func GenerateStlEdbData(req *request.STLReq) (resp *response.StlPreviewResp, msg string, err error) {
+func GenerateStlEdbData(req *request.STLReq, adminId int) (resp *response.StlPreviewResp, msg string, err error) {
 	edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
 	if err != nil {
 		if err.Error() == utils.ErrNoRow() {
@@ -57,9 +58,61 @@ func GenerateStlEdbData(req *request.STLReq) (resp *response.StlPreviewResp, msg
 		msg = "获取指标数据失败"
 		return
 	}
+
+	var condMsg string
+	if req.Period < 2 || req.Period > len(edbData) {
+		condMsg += "period必须是一个大于等于2的正整数,且必须小于时间序列的长度"
+	}
+	if req.Seasonal < 3 || req.Seasonal%2 == 0 || req.Seasonal <= req.Period {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "seasonal必须是一个大于等于3的奇整数,且必须大于period"
+	}
+	if req.Trend < 3 || req.Trend%2 == 0 || req.Trend <= req.Period {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "trend必须是一个大于等于3的奇整数,且必须大于period"
+	}
+	if req.Fraction < 0 || req.Fraction > 1 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "fraction必须是一个介于[0-1]之间"
+	}
+	if 1 > req.TrendDeg || req.TrendDeg > 5 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "trend_deg请设置成1-5的整数"
+	}
+	if 1 > req.SeasonalDeg || req.SeasonalDeg > 5 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "seasonal_deg请设置成1-5的整数"
+	}
+	if 1 > req.LowPassDeg || req.LowPassDeg > 5 {
+		if condMsg != "" {
+			condMsg += "\n"
+		}
+		condMsg += "low_pass_deg请设置成1-5的整数"
+	}
+	if condMsg != "" {
+		msg = condMsg
+		err = fmt.Errorf("参数错误")
+		return
+	}
+
 	dir, _ := os.Executable()
-	exPath := filepath.Dir(dir)
-	loadFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	exPath := filepath.Dir(dir) + "/static/stl_tmp"
+	err = CheckOsPathAndMake(exPath)
+	if err != nil {
+		msg = "计算失败"
+		return
+	}
+	loadFilePath := exPath + "/" + strconv.Itoa(adminId) + "_" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
 	err = SaveToExcel(edbData, loadFilePath)
 	if err != nil {
 		msg = "保存数据到Excel失败"
@@ -67,16 +120,24 @@ func GenerateStlEdbData(req *request.STLReq) (resp *response.StlPreviewResp, msg
 	}
 	defer os.Remove(loadFilePath)
 
-	saveFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + "_res" + ".xlsx"
-	result, err := execStlPythonCode(loadFilePath, saveFilePath, req.Period, req.Seasonal, req.Trend, req.Fraction, req.TrendDeg, req.SeasonalDeg, req.LowPassDeg, req.Robust)
+	saveFilePath := exPath + "/" + strconv.Itoa(adminId) + "_" + time.Now().Format(utils.FormatDateTimeUnSpace) + "_res" + ".xlsx"
+	t1 := time.Now()
+	result, err := execStlPythonCode(loadFilePath, saveFilePath, req.Period, req.Seasonal, req.Trend, req.TrendDeg, req.SeasonalDeg, req.LowPassDeg, req.Fraction, req.Robust)
 	if err != nil {
 		msg = "计算失败,请重新选择指标和参数后计算"
 	}
+	fmt.Println("stl计算耗时:", time.Since(t1))
 	trendChart, seasonalChart, residualChart, err := ParseStlExcel(saveFilePath)
 	if err != nil {
 		msg = "解析Excel失败"
 		return
 	}
+	defer os.Remove(saveFilePath)
+	resp = new(response.StlPreviewResp)
+	resp.OriginEdbInfo.Title = edbInfo.EdbName
+	resp.OriginEdbInfo.Frequency = edbInfo.Frequency
+	resp.OriginEdbInfo.Unit = edbInfo.Unit
+	resp.OriginEdbInfo.DataList = formatEdbData(edbData)
 	resp.TrendChartInfo = trendChart
 	resp.TrendChartInfo.Title = edbInfo.EdbName + "Trend"
 	resp.TrendChartInfo.Frequency = edbInfo.Frequency
@@ -89,20 +150,37 @@ func GenerateStlEdbData(req *request.STLReq) (resp *response.StlPreviewResp, msg
 	resp.ResidualChartInfo.Title = edbInfo.EdbName + "Residual"
 	resp.ResidualChartInfo.Frequency = edbInfo.Frequency
 	resp.ResidualChartInfo.Unit = edbInfo.Unit
-	resp.EvaluationResult.Mean = result.ResidualMean
-	resp.EvaluationResult.Std = result.ResidualVar
-	resp.EvaluationResult.AdfPValue = result.AdfPValue
-	resp.EvaluationResult.LjungBoxPValue = result.LbTestPValue
+	resp.EvaluationResult.Mean = strconv.FormatFloat(result.ResidualMean, 'f', 4, 64)
+	resp.EvaluationResult.Std = strconv.FormatFloat(result.ResidualVar, 'f', 4, 64)
+	resp.EvaluationResult.AdfPValue = strconv.FormatFloat(result.AdfPValue, 'f', -1, 64)
+	resp.EvaluationResult.LjungBoxPValue = strconv.FormatFloat(result.LbTestPValue, 'f', -1, 64)
 
 	return
 }
 
+func formatEdbData(items []*data_manage.EdbData) []*response.EdbData {
+	res := make([]*response.EdbData, 0, len(items))
+	for _, item := range items {
+		res = append(res, &response.EdbData{
+			DataTime: item.DataTime,
+			Value:    strconv.FormatFloat(item.Value, 'f', -1, 64),
+		})
+	}
+	return res
+}
+
+func CheckOsPathAndMake(path string) (err error) {
+	if _, er := os.Stat(path); os.IsNotExist(er) {
+		err = os.MkdirAll(path, os.ModePerm)
+	}
+	return
+}
+
 func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart response.ChartEdbInfo, err error) {
 	file, err := xlsx.OpenFile(excelPath)
 	if err != nil {
 		return
 	}
-	defer os.Remove(excelPath)
 	for _, sheet := range file.Sheets {
 		switch sheet.Name {
 		case "季节":
@@ -112,7 +190,8 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
 				}
 				date := row.Cells[0].String()
 				date = strings.Split(date, " ")[0]
-				value := row.Cells[1].String()
+				fv, _ := row.Cells[1].Float()
+				value := strconv.FormatFloat(fv, 'f', 4, 64)
 				SeasonalChart.DataList = append(SeasonalChart.DataList, &response.EdbData{DataTime: date, Value: value})
 			}
 		case "趋势":
@@ -122,7 +201,8 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
 				}
 				date := row.Cells[0].String()
 				date = strings.Split(date, " ")[0]
-				value := row.Cells[1].String()
+				fv, _ := row.Cells[1].Float()
+				value := strconv.FormatFloat(fv, 'f', 4, 64)
 				TrendChart.DataList = append(TrendChart.DataList, &response.EdbData{DataTime: date, Value: value})
 			}
 		case "残差":
@@ -132,7 +212,8 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
 				}
 				date := row.Cells[0].String()
 				date = strings.Split(date, " ")[0]
-				value := row.Cells[1].String()
+				fv, _ := row.Cells[1].Float()
+				value := strconv.FormatFloat(fv, 'f', 4, 64)
 				ResidualChart.DataList = append(ResidualChart.DataList, &response.EdbData{DataTime: date, Value: value})
 			}
 		}
@@ -163,14 +244,14 @@ func SaveToExcel(data []*data_manage.EdbData, filePath string) (err error) {
 }
 
 type STLResult struct {
-	ResidualMean string `json:"residual_mean"`
-	ResidualVar  string `json:"residual_var"`
-	AdfPValue    string `json:"adf_p_value"`
-	LbTestPValue string `json:"lb_test_p_value"`
-	LbTestStat   string `json:"lb_test_stat"`
+	ResidualMean float64 `json:"residual_mean"`
+	ResidualVar  float64 `json:"residual_var"`
+	AdfPValue    float64 `json:"adf_p_value"`
+	LbTestPValue float64 `json:"lb_test_p_value"`
+	LbTestStat   float64 `json:"lb_test_stat"`
 }
 
-func execStlPythonCode(path, toPath string, period, seasonal, trend, fraction, trendDeg, seasonalDeg, lowPassDeg int, robust bool) (stlResult *STLResult, err error) {
+func execStlPythonCode(path, toPath string, period, seasonal, trend, trendDeg, seasonalDeg, lowPassDeg int, fraction float64, robust bool) (stlResult *STLResult, err error) {
 	pythonCode := `
 import pandas as pd
 from statsmodels.tsa.seasonal import STL
@@ -179,6 +260,8 @@ from statsmodels.tsa.stattools import adfuller
 from statsmodels.stats.diagnostic import acorr_ljungbox
 import numpy as np
 import json
+import warnings
+warnings.filterwarnings('ignore')
 
 file_path = r"%s"
 df = pd.read_excel(file_path, parse_dates=['日期'])
@@ -188,7 +271,7 @@ df.set_index('日期', inplace=True)
 period = %d
 seasonal = %d
 trend = %d
-fraction = %d
+fraction = %g
 seasonal_deg = %d
 trend_deg = %d
 low_pass_deg = %d
@@ -256,7 +339,7 @@ print(output)
 	}
 
 	pythonCode = fmt.Sprintf(pythonCode, path, period, seasonal, trend, fraction, seasonalDeg, trendDeg, lowPassDeg, robustStr, toPath)
-	cmd := exec.Command("python3", "-c", pythonCode)
+	cmd := exec.Command(`D:\conda\envs\py311\python`, "-c", pythonCode)
 	output, err := cmd.CombinedOutput()
 	if err != nil {
 		return