|
@@ -10,6 +10,7 @@ import (
|
|
"os"
|
|
"os"
|
|
"os/exec"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"path/filepath"
|
|
|
|
+ "strconv"
|
|
"strings"
|
|
"strings"
|
|
"time"
|
|
"time"
|
|
|
|
|
|
@@ -23,7 +24,7 @@ const (
|
|
RANGE_DATE_TO_NOW
|
|
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)
|
|
edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
|
|
if err != nil {
|
|
if err != nil {
|
|
if err.Error() == utils.ErrNoRow() {
|
|
if err.Error() == utils.ErrNoRow() {
|
|
@@ -57,9 +58,61 @@ func GenerateStlEdbData(req *request.STLReq) (resp *response.StlPreviewResp, msg
|
|
msg = "获取指标数据失败"
|
|
msg = "获取指标数据失败"
|
|
return
|
|
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()
|
|
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)
|
|
err = SaveToExcel(edbData, loadFilePath)
|
|
if err != nil {
|
|
if err != nil {
|
|
msg = "保存数据到Excel失败"
|
|
msg = "保存数据到Excel失败"
|
|
@@ -67,16 +120,24 @@ func GenerateStlEdbData(req *request.STLReq) (resp *response.StlPreviewResp, msg
|
|
}
|
|
}
|
|
defer os.Remove(loadFilePath)
|
|
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 {
|
|
if err != nil {
|
|
msg = "计算失败,请重新选择指标和参数后计算"
|
|
msg = "计算失败,请重新选择指标和参数后计算"
|
|
}
|
|
}
|
|
|
|
+ fmt.Println("stl计算耗时:", time.Since(t1))
|
|
trendChart, seasonalChart, residualChart, err := ParseStlExcel(saveFilePath)
|
|
trendChart, seasonalChart, residualChart, err := ParseStlExcel(saveFilePath)
|
|
if err != nil {
|
|
if err != nil {
|
|
msg = "解析Excel失败"
|
|
msg = "解析Excel失败"
|
|
return
|
|
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 = trendChart
|
|
resp.TrendChartInfo.Title = edbInfo.EdbName + "Trend"
|
|
resp.TrendChartInfo.Title = edbInfo.EdbName + "Trend"
|
|
resp.TrendChartInfo.Frequency = edbInfo.Frequency
|
|
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.Title = edbInfo.EdbName + "Residual"
|
|
resp.ResidualChartInfo.Frequency = edbInfo.Frequency
|
|
resp.ResidualChartInfo.Frequency = edbInfo.Frequency
|
|
resp.ResidualChartInfo.Unit = edbInfo.Unit
|
|
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
|
|
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) {
|
|
func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart response.ChartEdbInfo, err error) {
|
|
file, err := xlsx.OpenFile(excelPath)
|
|
file, err := xlsx.OpenFile(excelPath)
|
|
if err != nil {
|
|
if err != nil {
|
|
return
|
|
return
|
|
}
|
|
}
|
|
- defer os.Remove(excelPath)
|
|
|
|
for _, sheet := range file.Sheets {
|
|
for _, sheet := range file.Sheets {
|
|
switch sheet.Name {
|
|
switch sheet.Name {
|
|
case "季节":
|
|
case "季节":
|
|
@@ -112,7 +190,8 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
|
|
}
|
|
}
|
|
date := row.Cells[0].String()
|
|
date := row.Cells[0].String()
|
|
date = strings.Split(date, " ")[0]
|
|
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})
|
|
SeasonalChart.DataList = append(SeasonalChart.DataList, &response.EdbData{DataTime: date, Value: value})
|
|
}
|
|
}
|
|
case "趋势":
|
|
case "趋势":
|
|
@@ -122,7 +201,8 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
|
|
}
|
|
}
|
|
date := row.Cells[0].String()
|
|
date := row.Cells[0].String()
|
|
date = strings.Split(date, " ")[0]
|
|
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})
|
|
TrendChart.DataList = append(TrendChart.DataList, &response.EdbData{DataTime: date, Value: value})
|
|
}
|
|
}
|
|
case "残差":
|
|
case "残差":
|
|
@@ -132,7 +212,8 @@ func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart r
|
|
}
|
|
}
|
|
date := row.Cells[0].String()
|
|
date := row.Cells[0].String()
|
|
date = strings.Split(date, " ")[0]
|
|
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})
|
|
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 {
|
|
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 := `
|
|
pythonCode := `
|
|
import pandas as pd
|
|
import pandas as pd
|
|
from statsmodels.tsa.seasonal import STL
|
|
from statsmodels.tsa.seasonal import STL
|
|
@@ -179,6 +260,8 @@ from statsmodels.tsa.stattools import adfuller
|
|
from statsmodels.stats.diagnostic import acorr_ljungbox
|
|
from statsmodels.stats.diagnostic import acorr_ljungbox
|
|
import numpy as np
|
|
import numpy as np
|
|
import json
|
|
import json
|
|
|
|
+import warnings
|
|
|
|
+warnings.filterwarnings('ignore')
|
|
|
|
|
|
file_path = r"%s"
|
|
file_path = r"%s"
|
|
df = pd.read_excel(file_path, parse_dates=['日期'])
|
|
df = pd.read_excel(file_path, parse_dates=['日期'])
|
|
@@ -188,7 +271,7 @@ df.set_index('日期', inplace=True)
|
|
period = %d
|
|
period = %d
|
|
seasonal = %d
|
|
seasonal = %d
|
|
trend = %d
|
|
trend = %d
|
|
-fraction = %d
|
|
|
|
|
|
+fraction = %g
|
|
seasonal_deg = %d
|
|
seasonal_deg = %d
|
|
trend_deg = %d
|
|
trend_deg = %d
|
|
low_pass_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)
|
|
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()
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
if err != nil {
|
|
return
|
|
return
|