stl.go 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032
  1. package stl
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "eta/eta_api/models/data_manage"
  6. "eta/eta_api/models/data_manage/stl"
  7. "eta/eta_api/models/data_manage/stl/request"
  8. "eta/eta_api/models/data_manage/stl/response"
  9. "eta/eta_api/services/data"
  10. "eta/eta_api/services/data/data_manage_permission"
  11. "eta/eta_api/services/elastic"
  12. "eta/eta_api/utils"
  13. "fmt"
  14. "os"
  15. "os/exec"
  16. "path/filepath"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "github.com/rdlucklib/rdluck_tools/paging"
  21. "github.com/tealeg/xlsx"
  22. )
  23. const (
  24. ALL_DATE = iota + 1
  25. LAST_N_YEARS
  26. RANGE_DATE
  27. RANGE_DATE_TO_NOW
  28. )
  29. var EDB_DATA_CALCULATE_STL_TREND_CACHE = `eta:stl_decompose:trend:config_id:`
  30. var EDB_DATA_CALCULATE_STL_SEASONAL_CACHE = `eta:stl_decompose:seasonal:config_id:`
  31. var EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE = `eta:stl_decompose:residual:config_id:`
  32. func GenerateStlEdbData(req *request.StlConfigReq, adminId int) (resp *response.StlPreviewResp, msg string, err error) {
  33. config, err := stl.GetCalculateStlConfigById(req.CalculateStlConfigId)
  34. if err != nil {
  35. if err.Error() == utils.ErrNoRow() {
  36. msg = "配置信息不存在,请重新计算"
  37. return
  38. }
  39. msg = "获取配置信息失败"
  40. return
  41. }
  42. var confReq request.StlConfigReq
  43. if err = json.Unmarshal([]byte(config.Config), &confReq); err != nil {
  44. msg = "预览失败"
  45. err = fmt.Errorf("配置信息解析失败, err:%s", err.Error())
  46. return
  47. }
  48. edbInfo, err := data_manage.GetEdbInfoById(confReq.EdbInfoId)
  49. if err != nil {
  50. if err.Error() == utils.ErrNoRow() {
  51. msg = "指标不存在"
  52. return
  53. }
  54. msg = "获取指标信息失败"
  55. return
  56. }
  57. var condition string
  58. var pars []interface{}
  59. switch confReq.DataRangeType {
  60. case ALL_DATE:
  61. case LAST_N_YEARS:
  62. condition += " AND data_time >=?"
  63. year := time.Now().Year()
  64. lastDate := time.Date(year-confReq.LastNYear, 1, 1, 0, 0, 0, 0, time.Local)
  65. pars = append(pars, lastDate)
  66. case RANGE_DATE:
  67. condition = " AND data_time >=? AND data_time <=?"
  68. pars = append(pars, confReq.StartDate, confReq.EndDate)
  69. case RANGE_DATE_TO_NOW:
  70. condition = " AND data_time >=?"
  71. pars = append(pars, confReq.StartDate)
  72. }
  73. condition += " AND edb_code =?"
  74. pars = append(pars, edbInfo.EdbCode)
  75. edbData, err := data_manage.GetAllEdbDataListByCondition(condition, pars, edbInfo.Source, edbInfo.SubSource)
  76. if err != nil {
  77. msg = "获取指标数据失败"
  78. return
  79. }
  80. var condMsg string
  81. if confReq.Period < 2 || confReq.Period > len(edbData) {
  82. condMsg += "period必须是一个大于等于2的正整数,且必须小于时间序列的长度"
  83. }
  84. if confReq.Seasonal < 3 || confReq.Seasonal%2 == 0 || confReq.Seasonal <= confReq.Period {
  85. if condMsg != "" {
  86. condMsg += "\n"
  87. }
  88. condMsg += "seasonal必须是一个大于等于3的奇整数,且必须大于period"
  89. }
  90. if confReq.Trend < 3 || confReq.Trend%2 == 0 || confReq.Trend <= confReq.Period {
  91. if condMsg != "" {
  92. condMsg += "\n"
  93. }
  94. condMsg += "trend必须是一个大于等于3的奇整数,且必须大于period"
  95. }
  96. if confReq.Fraction < 0 || confReq.Fraction > 1 {
  97. if condMsg != "" {
  98. condMsg += "\n"
  99. }
  100. condMsg += "fraction必须是一个介于[0-1]之间"
  101. }
  102. if 1 > confReq.TrendDeg || confReq.TrendDeg > 5 {
  103. if condMsg != "" {
  104. condMsg += "\n"
  105. }
  106. condMsg += "trend_deg请设置成1-5的整数"
  107. }
  108. if 1 > confReq.SeasonalDeg || confReq.SeasonalDeg > 5 {
  109. if condMsg != "" {
  110. condMsg += "\n"
  111. }
  112. condMsg += "seasonal_deg请设置成1-5的整数"
  113. }
  114. if 1 > confReq.LowPassDeg || confReq.LowPassDeg > 5 {
  115. if condMsg != "" {
  116. condMsg += "\n"
  117. }
  118. condMsg += "low_pass_deg请设置成1-5的整数"
  119. }
  120. if condMsg != "" {
  121. msg = condMsg
  122. err = fmt.Errorf("参数错误")
  123. return
  124. }
  125. dir, _ := os.Executable()
  126. exPath := filepath.Dir(dir) + "/static/stl_tmp"
  127. err = CheckOsPathAndMake(exPath)
  128. if err != nil {
  129. msg = "计算失败"
  130. return
  131. }
  132. loadFilePath := exPath + "/" + strconv.Itoa(adminId) + "_" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
  133. err = SaveToExcel(edbData, loadFilePath)
  134. if err != nil {
  135. msg = "保存数据到Excel失败"
  136. return
  137. }
  138. defer os.Remove(loadFilePath)
  139. saveFilePath := exPath + "/" + strconv.Itoa(adminId) + "_" + time.Now().Format(utils.FormatDateTimeUnSpace) + "_res" + ".xlsx"
  140. result, err := execStlPythonCode(loadFilePath, saveFilePath, confReq.Period, confReq.Seasonal, confReq.Trend, confReq.TrendDeg, confReq.SeasonalDeg, confReq.LowPassDeg, confReq.Fraction, confReq.Robust)
  141. if err != nil {
  142. msg = "计算失败,请重新选择指标和参数后计算"
  143. return
  144. }
  145. trendChart, seasonalChart, residualChart, err := ParseStlExcel(saveFilePath)
  146. if err != nil {
  147. msg = "解析Excel失败"
  148. return
  149. }
  150. defer os.Remove(saveFilePath)
  151. resp = new(response.StlPreviewResp)
  152. resp.OriginEdbInfo.Title = edbInfo.EdbName
  153. resp.OriginEdbInfo.ClassifyId = edbInfo.ClassifyId
  154. resp.OriginEdbInfo.MaxData = edbInfo.MaxValue
  155. resp.OriginEdbInfo.MinData = edbInfo.MinValue
  156. resp.OriginEdbInfo.Frequency = edbInfo.Frequency
  157. resp.OriginEdbInfo.Unit = edbInfo.Unit
  158. resp.OriginEdbInfo.DataList = formatEdbData(edbData)
  159. resp.TrendChartInfo.DataList = trendChart.DataList
  160. resp.TrendChartInfo.MaxData = trendChart.MaxData
  161. resp.TrendChartInfo.MinData = trendChart.MinData
  162. resp.TrendChartInfo.Title = edbInfo.EdbName + "Trend"
  163. resp.TrendChartInfo.ClassifyId = edbInfo.ClassifyId
  164. resp.TrendChartInfo.Frequency = edbInfo.Frequency
  165. resp.TrendChartInfo.Unit = edbInfo.Unit
  166. resp.SeasonalChartInfo.DataList = seasonalChart.DataList
  167. resp.SeasonalChartInfo.MaxData = seasonalChart.MaxData
  168. resp.SeasonalChartInfo.MinData = seasonalChart.MinData
  169. resp.SeasonalChartInfo.ClassifyId = edbInfo.ClassifyId
  170. resp.SeasonalChartInfo.Title = edbInfo.EdbName + "Seasonal"
  171. resp.SeasonalChartInfo.Frequency = edbInfo.Frequency
  172. resp.SeasonalChartInfo.Unit = edbInfo.Unit
  173. resp.ResidualChartInfo.DataList = residualChart.DataList
  174. resp.ResidualChartInfo.MaxData = residualChart.MaxData
  175. resp.ResidualChartInfo.MinData = residualChart.MinData
  176. resp.ResidualChartInfo.ClassifyId = edbInfo.ClassifyId
  177. resp.ResidualChartInfo.Title = edbInfo.EdbName + "Residual"
  178. resp.ResidualChartInfo.Frequency = edbInfo.Frequency
  179. resp.ResidualChartInfo.Unit = edbInfo.Unit
  180. resp.EvaluationResult.Mean = strconv.FormatFloat(result.ResidualMean, 'f', 4, 64)
  181. resp.EvaluationResult.Std = strconv.FormatFloat(result.ResidualVar, 'f', 4, 64)
  182. resp.EvaluationResult.AdfPValue = strconv.FormatFloat(result.AdfPValue, 'f', -1, 64)
  183. resp.EvaluationResult.LjungBoxPValue = strconv.FormatFloat(result.LbTestPValue, 'f', -1, 64)
  184. bTrend, _ := json.Marshal(trendChart.DataList)
  185. bSeasonal, _ := json.Marshal(seasonalChart.DataList)
  186. bResidual, _ := json.Marshal(residualChart.DataList)
  187. utils.Rc.Put(EDB_DATA_CALCULATE_STL_TREND_CACHE+strconv.Itoa(config.CalculateStlConfigId), bTrend, time.Hour*2)
  188. utils.Rc.Put(EDB_DATA_CALCULATE_STL_SEASONAL_CACHE+strconv.Itoa(config.CalculateStlConfigId), bSeasonal, time.Hour*2)
  189. utils.Rc.Put(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE+strconv.Itoa(config.CalculateStlConfigId), bResidual, time.Hour*2)
  190. return
  191. }
  192. func formatEdbData(items []*data_manage.EdbData) []*response.EdbData {
  193. res := make([]*response.EdbData, 0, len(items))
  194. for _, item := range items {
  195. t, _ := time.Parse(utils.FormatDate, item.DataTime)
  196. res = append(res, &response.EdbData{
  197. DataTime: item.DataTime,
  198. Value: item.Value,
  199. DataTimestamp: t.UnixMilli(),
  200. })
  201. }
  202. return res
  203. }
  204. func CheckOsPathAndMake(path string) (err error) {
  205. if _, er := os.Stat(path); os.IsNotExist(er) {
  206. err = os.MkdirAll(path, os.ModePerm)
  207. }
  208. return
  209. }
  210. func ParseStlExcel(excelPath string) (TrendChart, SeasonalChart, ResidualChart response.ChartEdbInfo, err error) {
  211. file, err := xlsx.OpenFile(excelPath)
  212. if err != nil {
  213. return
  214. }
  215. for _, sheet := range file.Sheets {
  216. switch sheet.Name {
  217. case "季节":
  218. var MinData, MaxData float64
  219. for i, row := range sheet.Rows {
  220. if i == 0 {
  221. continue
  222. }
  223. var date string
  224. var dataTimestamp int64
  225. if row.Cells[0].Type() == xlsx.CellTypeNumeric {
  226. dataNum, _ := strconv.ParseFloat(row.Cells[0].Value, 64)
  227. tmpTime := xlsx.TimeFromExcelTime(dataNum, false)
  228. date = tmpTime.Format(utils.FormatDate)
  229. dataTimestamp = tmpTime.UnixMilli()
  230. } else {
  231. timeDate, _ := time.Parse(utils.FormatDateTime, date)
  232. date = timeDate.Format(utils.FormatDate)
  233. dataTimestamp = timeDate.UnixMilli()
  234. }
  235. fv, _ := row.Cells[1].Float()
  236. if MinData == 0 || fv < MinData {
  237. MinData = fv
  238. }
  239. if MaxData == 0 || fv > MaxData {
  240. MaxData = fv
  241. }
  242. SeasonalChart.DataList = append(SeasonalChart.DataList, &response.EdbData{DataTime: date, Value: fv, DataTimestamp: dataTimestamp})
  243. }
  244. SeasonalChart.MinData = MinData
  245. SeasonalChart.MaxData = MaxData
  246. case "趋势":
  247. var MinData, MaxData float64
  248. for i, row := range sheet.Rows {
  249. if i == 0 {
  250. continue
  251. }
  252. var date string
  253. var dataTimestamp int64
  254. if row.Cells[0].Type() == xlsx.CellTypeNumeric {
  255. dataNum, _ := strconv.ParseFloat(row.Cells[0].Value, 64)
  256. tmpTime := xlsx.TimeFromExcelTime(dataNum, false)
  257. date = tmpTime.Format(utils.FormatDate)
  258. dataTimestamp = tmpTime.UnixMilli()
  259. } else {
  260. timeDate, _ := time.Parse(utils.FormatDateTime, date)
  261. date = timeDate.Format(utils.FormatDate)
  262. dataTimestamp = timeDate.UnixMilli()
  263. }
  264. fv, _ := row.Cells[1].Float()
  265. if MinData == 0 || fv < MinData {
  266. MinData = fv
  267. }
  268. if MaxData == 0 || fv > MaxData {
  269. MaxData = fv
  270. }
  271. TrendChart.DataList = append(TrendChart.DataList, &response.EdbData{DataTime: date, Value: fv, DataTimestamp: dataTimestamp})
  272. }
  273. TrendChart.MaxData = MaxData
  274. TrendChart.MinData = MinData
  275. case "残差":
  276. var MinData, MaxData float64
  277. for i, row := range sheet.Rows {
  278. if i == 0 {
  279. continue
  280. }
  281. var date string
  282. var dataTimestamp int64
  283. if row.Cells[0].Type() == xlsx.CellTypeNumeric {
  284. dataNum, _ := strconv.ParseFloat(row.Cells[0].Value, 64)
  285. tmpTime := xlsx.TimeFromExcelTime(dataNum, false)
  286. date = tmpTime.Format(utils.FormatDate)
  287. dataTimestamp = tmpTime.UnixMilli()
  288. } else {
  289. timeDate, _ := time.Parse(utils.FormatDateTime, date)
  290. date = timeDate.Format(utils.FormatDate)
  291. dataTimestamp = timeDate.UnixMilli()
  292. }
  293. fv, _ := row.Cells[1].Float()
  294. if MinData == 0 || fv < MinData {
  295. MinData = fv
  296. }
  297. if MaxData == 0 || fv > MaxData {
  298. MaxData = fv
  299. }
  300. ResidualChart.DataList = append(ResidualChart.DataList, &response.EdbData{DataTime: date, Value: fv, DataTimestamp: dataTimestamp})
  301. }
  302. ResidualChart.MaxData = MaxData
  303. ResidualChart.MinData = MinData
  304. }
  305. }
  306. return
  307. }
  308. func SaveToExcel(data []*data_manage.EdbData, filePath string) (err error) {
  309. xlsxFile := xlsx.NewFile()
  310. sheetNew, err := xlsxFile.AddSheet("Tmp")
  311. if err != nil {
  312. return
  313. }
  314. titleRow := sheetNew.AddRow()
  315. titleRow.AddCell().SetString("日期")
  316. titleRow.AddCell().SetString("值")
  317. for i, d := range data {
  318. row := sheetNew.Row(i + 1)
  319. row.AddCell().SetString(d.DataTime)
  320. row.AddCell().SetFloat(d.Value)
  321. }
  322. err = xlsxFile.Save(filePath)
  323. if err != nil {
  324. return
  325. }
  326. return
  327. }
  328. type STLResult struct {
  329. ResidualMean float64 `json:"residual_mean"`
  330. ResidualVar float64 `json:"residual_var"`
  331. AdfPValue float64 `json:"adf_p_value"`
  332. LbTestPValue float64 `json:"lb_test_p_value"`
  333. LbTestStat float64 `json:"lb_test_stat"`
  334. }
  335. func execStlPythonCode(path, toPath string, period, seasonal, trend, trendDeg, seasonalDeg, lowPassDeg int, fraction float64, robust bool) (stlResult *STLResult, err error) {
  336. pythonCode := `
  337. import pandas as pd
  338. from statsmodels.tsa.seasonal import STL
  339. from statsmodels.nonparametric.smoothers_lowess import lowess
  340. from statsmodels.tsa.stattools import adfuller
  341. from statsmodels.stats.diagnostic import acorr_ljungbox
  342. import numpy as np
  343. import json
  344. import warnings
  345. warnings.filterwarnings('ignore')
  346. file_path = r"%s"
  347. df = pd.read_excel(file_path, parse_dates=['日期'])
  348. df.set_index('日期', inplace=True)
  349. period = %d
  350. seasonal = %d
  351. trend = %d
  352. fraction = %g
  353. seasonal_deg = %d
  354. trend_deg = %d
  355. low_pass_deg = %d
  356. robust = %s
  357. stl = STL(
  358. df['值'],
  359. period=period,
  360. seasonal=seasonal,
  361. trend=trend,
  362. low_pass=None,
  363. seasonal_deg=seasonal_deg,
  364. trend_deg=trend_deg,
  365. low_pass_deg=low_pass_deg,
  366. seasonal_jump=1,
  367. trend_jump=1,
  368. low_pass_jump=1,
  369. robust=robust
  370. )
  371. result = stl.fit()
  372. smoothed = lowess(df['值'], df.index, frac=fraction)
  373. trend_lowess = smoothed[:, 1]
  374. # 季节图
  375. seasonal_component = result.seasonal
  376. # 趋势图
  377. trend_lowess_series = pd.Series(trend_lowess, index=df.index)
  378. # 残差图
  379. residual_component = df['值'] - trend_lowess - seasonal_component
  380. # 计算打印残差的均值
  381. residual_mean = np.mean(residual_component)
  382. # 计算打印残差的方差
  383. residual_var = np.std(residual_component)
  384. # 计算打印残差的ADF检验结果, 输出p-value
  385. adf_result = adfuller(residual_component)
  386. # 根据p-value判断是否平稳
  387. lb_test = acorr_ljungbox(residual_component, lags=period, return_df=True)
  388. output_file = r"%s"
  389. with pd.ExcelWriter(output_file) as writer:
  390. # 保存季节图
  391. pd.Series(seasonal_component, index=df.index, name='值').to_frame().reset_index().rename(columns={'index': '日期'}).to_excel(writer, sheet_name='季节', index=False)
  392. # 保存趋势图
  393. trend_lowess_series.to_frame(name='值').reset_index().rename(columns={'index': '日期'}).to_excel(writer, sheet_name='趋势', index=False)
  394. # 保存残差图
  395. pd.Series(residual_component, index=df.index, name='值').to_frame().reset_index().rename(columns={'index': '日期'}).to_excel(writer, sheet_name='残差', index=False)
  396. output = json.dumps({
  397. 'residual_mean': residual_mean,
  398. 'residual_var': residual_var,
  399. 'adf_p_value': adf_result[1],
  400. 'lb_test_p_value': lb_test['lb_pvalue'].values[0],
  401. 'lb_test_stat': lb_test['lb_stat'].values[0]
  402. })
  403. print(output)
  404. `
  405. robustStr := "True"
  406. if !robust {
  407. robustStr = "False"
  408. }
  409. pythonCode = fmt.Sprintf(pythonCode, path, period, seasonal, trend, fraction, seasonalDeg, trendDeg, lowPassDeg, robustStr, toPath)
  410. cmd := exec.Command(`python3`, "-c", pythonCode)
  411. output, err := cmd.CombinedOutput()
  412. if err != nil {
  413. return
  414. }
  415. defer cmd.Process.Kill()
  416. if err = json.Unmarshal(output, &stlResult); err != nil {
  417. return
  418. }
  419. return
  420. }
  421. func SaveStlConfig(req *request.StlConfigReq, adminId int) (configId int64, msg string, err error) {
  422. edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
  423. if err != nil {
  424. if err.Error() == utils.ErrNoRow() {
  425. msg = "指标不存在"
  426. return
  427. }
  428. msg = "获取指标信息失败"
  429. return
  430. }
  431. var condition string
  432. var pars []interface{}
  433. switch req.DataRangeType {
  434. case ALL_DATE:
  435. case LAST_N_YEARS:
  436. condition += " AND data_time >=?"
  437. year := time.Now().Year()
  438. lastDate := time.Date(year-req.LastNYear, 1, 1, 0, 0, 0, 0, time.Local)
  439. pars = append(pars, lastDate)
  440. case RANGE_DATE:
  441. condition = " AND data_time >=? AND data_time <=?"
  442. pars = append(pars, req.StartDate, req.EndDate)
  443. case RANGE_DATE_TO_NOW:
  444. condition = " AND data_time >=?"
  445. pars = append(pars, req.StartDate)
  446. }
  447. condition += " AND edb_code =?"
  448. pars = append(pars, edbInfo.EdbCode)
  449. edbData, err := data_manage.GetAllEdbDataListByCondition(condition, pars, edbInfo.Source, edbInfo.SubSource)
  450. if err != nil {
  451. msg = "获取指标数据失败"
  452. return
  453. }
  454. var condMsg string
  455. if req.Period < 2 || req.Period > len(edbData) {
  456. condMsg += "period必须是一个大于等于2的正整数,且必须小于时间序列的长度"
  457. }
  458. if req.Seasonal < 3 || req.Seasonal%2 == 0 || req.Seasonal <= req.Period {
  459. if condMsg != "" {
  460. condMsg += "\n"
  461. }
  462. condMsg += "seasonal必须是一个大于等于3的奇整数,且必须大于period"
  463. }
  464. if req.Trend < 3 || req.Trend%2 == 0 || req.Trend <= req.Period {
  465. if condMsg != "" {
  466. condMsg += "\n"
  467. }
  468. condMsg += "trend必须是一个大于等于3的奇整数,且必须大于period"
  469. }
  470. if req.Fraction < 0 || req.Fraction > 1 {
  471. if condMsg != "" {
  472. condMsg += "\n"
  473. }
  474. condMsg += "fraction必须是一个介于[0-1]之间"
  475. }
  476. if 1 > req.TrendDeg || req.TrendDeg > 5 {
  477. if condMsg != "" {
  478. condMsg += "\n"
  479. }
  480. condMsg += "trend_deg请设置成1-5的整数"
  481. }
  482. if 1 > req.SeasonalDeg || req.SeasonalDeg > 5 {
  483. if condMsg != "" {
  484. condMsg += "\n"
  485. }
  486. condMsg += "seasonal_deg请设置成1-5的整数"
  487. }
  488. if 1 > req.LowPassDeg || req.LowPassDeg > 5 {
  489. if condMsg != "" {
  490. condMsg += "\n"
  491. }
  492. condMsg += "low_pass_deg请设置成1-5的整数"
  493. }
  494. if condMsg != "" {
  495. msg = condMsg
  496. err = fmt.Errorf("参数错误")
  497. return
  498. }
  499. b, err := json.Marshal(req)
  500. if err != nil {
  501. return
  502. }
  503. conf := new(stl.CalculateStlConfig)
  504. if req.CalculateStlConfigId > 0 {
  505. conf.CalculateStlConfigId = req.CalculateStlConfigId
  506. conf.Config = string(b)
  507. conf.ModifyTime = time.Now()
  508. err = conf.Update([]string{"Config", "ModifyTime"})
  509. configId = int64(req.CalculateStlConfigId)
  510. } else {
  511. conf.Config = string(b)
  512. conf.SysUserId = adminId
  513. conf.CreateTime = time.Now()
  514. conf.ModifyTime = time.Now()
  515. configId, err = conf.Insert()
  516. }
  517. return
  518. }
  519. func SearchEdbInfoWithStl(adminId int, keyWord string, currentIndex, pageSize int) (resp data_manage.EdbInfoFilterDataResp, msg string, err error) {
  520. var edbInfoList []*data_manage.EdbInfoList
  521. noPermissionEdbInfoIdList := make([]int, 0) //无权限指标
  522. // 获取当前账号的不可见指标
  523. {
  524. obj := data_manage.EdbInfoNoPermissionAdmin{}
  525. confList, er := obj.GetAllListByAdminId(adminId)
  526. if er != nil && er.Error() != utils.ErrNoRow() {
  527. msg = "获取失败"
  528. err = fmt.Errorf("获取不可见指标配置数据失败,Err:" + er.Error())
  529. return
  530. }
  531. for _, v := range confList {
  532. noPermissionEdbInfoIdList = append(noPermissionEdbInfoIdList, v.EdbInfoId)
  533. }
  534. }
  535. if currentIndex <= 0 {
  536. currentIndex = 1
  537. }
  538. startSize := utils.StartIndex(currentIndex, pageSize)
  539. // 是否走ES
  540. isEs := false
  541. var total int64
  542. if keyWord != "" {
  543. frequencyList := []string{"日度", "周度", "旬度", "月度", "季度"}
  544. // 普通的搜索
  545. total, edbInfoList, err = elastic.SearchEdbInfoDataByfrequency(utils.DATA_INDEX_NAME, keyWord, startSize, pageSize, 0, frequencyList, noPermissionEdbInfoIdList)
  546. isEs = true
  547. } else {
  548. var condition string
  549. var pars []interface{}
  550. // 普通指标
  551. condition += ` AND edb_info_type = ? `
  552. pars = append(pars, 0)
  553. // 无权限指标id
  554. lenNoPermissionEdbInfoIdList := len(noPermissionEdbInfoIdList)
  555. if lenNoPermissionEdbInfoIdList > 0 {
  556. condition += ` AND edb_info_id not in (` + utils.GetOrmInReplace(lenNoPermissionEdbInfoIdList) + `) `
  557. pars = append(pars, noPermissionEdbInfoIdList)
  558. }
  559. //频度
  560. condition += ` AND frequency IN ('日度', '周度', '旬度', '月度', '季度') `
  561. total, edbInfoList, err = data_manage.GetEdbInfoFilterList(condition, pars, startSize, pageSize)
  562. }
  563. if err != nil {
  564. edbInfoList = make([]*data_manage.EdbInfoList, 0)
  565. }
  566. page := paging.GetPaging(currentIndex, pageSize, int(total))
  567. edbInfoListLen := len(edbInfoList)
  568. classifyIdList := make([]int, 0)
  569. for i := 0; i < edbInfoListLen; i++ {
  570. edbInfoList[i].EdbNameAlias = edbInfoList[i].EdbName
  571. classifyIdList = append(classifyIdList, edbInfoList[i].ClassifyId)
  572. }
  573. // 当前列表中的分类map
  574. classifyMap := make(map[int]*data_manage.EdbClassify)
  575. if edbInfoListLen > 0 {
  576. classifyList, er := data_manage.GetEdbClassifyByIdList(classifyIdList)
  577. if er != nil {
  578. msg = "获取失败"
  579. err = fmt.Errorf("获取分类列表失败,Err:" + er.Error())
  580. return
  581. }
  582. for _, v := range classifyList {
  583. classifyMap[v.ClassifyId] = v
  584. }
  585. // 获取所有有权限的指标和分类
  586. permissionEdbIdList, permissionClassifyIdList, er := data_manage_permission.GetUserEdbAndClassifyPermissionList(adminId, 0, 0)
  587. if er != nil {
  588. msg = "获取失败"
  589. err = fmt.Errorf("获取所有有权限的指标和分类失败,Err:" + er.Error())
  590. return
  591. }
  592. // 如果是ES的话,需要重新查一下指标的信息,主要是为了把是否授权字段找出来
  593. if isEs {
  594. edbInfoIdList := make([]int, 0)
  595. for i := 0; i < edbInfoListLen; i++ {
  596. edbInfoIdList = append(edbInfoIdList, edbInfoList[i].EdbInfoId)
  597. tmpEdbInfo := edbInfoList[i]
  598. if currClassify, ok := classifyMap[tmpEdbInfo.ClassifyId]; ok {
  599. edbInfoList[i].HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(tmpEdbInfo.IsJoinPermission, currClassify.IsJoinPermission, tmpEdbInfo.EdbInfoId, tmpEdbInfo.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
  600. }
  601. }
  602. tmpEdbList, er := data_manage.GetEdbInfoByIdList(edbInfoIdList)
  603. if er != nil {
  604. msg = "获取失败"
  605. err = fmt.Errorf("获取所有有权限的指标失败,Err:" + er.Error())
  606. return
  607. }
  608. edbInfoMap := make(map[int]*data_manage.EdbInfo)
  609. for _, v := range tmpEdbList {
  610. edbInfoMap[v.EdbInfoId] = v
  611. }
  612. for i := 0; i < edbInfoListLen; i++ {
  613. tmpEdbInfo, ok := edbInfoMap[edbInfoList[i].EdbInfoId]
  614. if !ok {
  615. continue
  616. }
  617. edbInfoList[i].IsJoinPermission = tmpEdbInfo.IsJoinPermission
  618. }
  619. }
  620. // 权限校验
  621. for i := 0; i < edbInfoListLen; i++ {
  622. tmpEdbInfoItem := edbInfoList[i]
  623. if currClassify, ok := classifyMap[tmpEdbInfoItem.ClassifyId]; ok {
  624. edbInfoList[i].HaveOperaAuth = data_manage_permission.CheckEdbPermissionByPermissionIdList(tmpEdbInfoItem.IsJoinPermission, currClassify.IsJoinPermission, tmpEdbInfoItem.EdbInfoId, tmpEdbInfoItem.ClassifyId, permissionEdbIdList, permissionClassifyIdList)
  625. }
  626. }
  627. }
  628. for i := 0; i < edbInfoListLen; i++ {
  629. for j := 0; j < edbInfoListLen; j++ {
  630. if (edbInfoList[i].EdbNameAlias == edbInfoList[j].EdbNameAlias) &&
  631. (edbInfoList[i].EdbInfoId != edbInfoList[j].EdbInfoId) &&
  632. !(strings.Contains(edbInfoList[i].EdbName, edbInfoList[i].SourceName)) {
  633. edbInfoList[i].EdbName = edbInfoList[i].EdbName + "(" + edbInfoList[i].SourceName + ")"
  634. }
  635. }
  636. }
  637. //新增搜索词记录
  638. {
  639. searchKeyword := new(data_manage.SearchKeyword)
  640. searchKeyword.KeyWord = keyWord
  641. searchKeyword.CreateTime = time.Now()
  642. go data_manage.AddSearchKeyword(searchKeyword)
  643. }
  644. resp = data_manage.EdbInfoFilterDataResp{
  645. Paging: page,
  646. List: edbInfoList,
  647. }
  648. return
  649. }
  650. func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName, lang string) (isSendEmail bool, msg string, err error) {
  651. if req.EdbName == "" {
  652. msg = "指标名称不能为空"
  653. return
  654. }
  655. if req.Unit == "" {
  656. msg = "指标单位不能为空"
  657. return
  658. }
  659. if req.ClassifyId <= 0 {
  660. msg = "请选择分类"
  661. return
  662. }
  663. if req.Frequency == "" {
  664. msg = "指标频度不能为空"
  665. return
  666. }
  667. conf, err := stl.GetCalculateStlConfigById(req.CalculateStlConfigId)
  668. if err != nil {
  669. if err.Error() == utils.ErrNoRow() {
  670. msg = "未找到配置,请先进行计算"
  671. err = fmt.Errorf("配置不存在")
  672. return
  673. }
  674. msg = "获取失败"
  675. return
  676. }
  677. var edbInfoData []*response.EdbData
  678. switch req.StlEdbType {
  679. case 1:
  680. // 趋势指标
  681. if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_TREND_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
  682. msg = "计算已过期,请重新计算"
  683. err = fmt.Errorf("not found")
  684. return
  685. }
  686. trendData, er := utils.Rc.RedisBytes(EDB_DATA_CALCULATE_STL_TREND_CACHE + strconv.Itoa(req.CalculateStlConfigId))
  687. if er != nil {
  688. msg = "获取失败"
  689. err = fmt.Errorf("获取redis数据失败,Err:" + er.Error())
  690. return
  691. }
  692. if er := json.Unmarshal(trendData, &edbInfoData); er != nil {
  693. msg = "获取失败"
  694. err = fmt.Errorf("json解析失败,Err:" + er.Error())
  695. return
  696. }
  697. case 2:
  698. // 季节性指标
  699. if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_SEASONAL_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
  700. msg = "计算已过期,请重新计算"
  701. err = fmt.Errorf("not found")
  702. return
  703. }
  704. seasonalData, er := utils.Rc.RedisBytes(EDB_DATA_CALCULATE_STL_SEASONAL_CACHE + strconv.Itoa(req.CalculateStlConfigId))
  705. if er != nil {
  706. msg = "获取失败"
  707. err = fmt.Errorf("获取redis数据失败,Err:" + er.Error())
  708. return
  709. }
  710. if er := json.Unmarshal(seasonalData, &edbInfoData); er != nil {
  711. msg = "获取失败"
  712. err = fmt.Errorf("json解析失败,Err:" + er.Error())
  713. return
  714. }
  715. case 3:
  716. // 残差性指标
  717. if ok := utils.Rc.IsExist(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE + strconv.Itoa(req.CalculateStlConfigId)); !ok {
  718. msg = "计算已过期,请重新计算"
  719. err = fmt.Errorf("not found")
  720. return
  721. }
  722. residualData, er := utils.Rc.RedisBytes(EDB_DATA_CALCULATE_STL_RESIDUAL_CACHE + strconv.Itoa(req.CalculateStlConfigId))
  723. if er != nil {
  724. msg = "获取失败"
  725. err = fmt.Errorf("获取redis数据失败,Err:" + er.Error())
  726. return
  727. }
  728. if er := json.Unmarshal(residualData, &edbInfoData); er != nil {
  729. msg = "获取失败"
  730. err = fmt.Errorf("json解析失败,Err:" + er.Error())
  731. return
  732. }
  733. default:
  734. msg = "获取失败"
  735. err = fmt.Errorf("未知的计算类型")
  736. return
  737. }
  738. indexObj := new(stl.EdbDataCalculateStl)
  739. edbCode, err := utils.GenerateEdbCode(1, "stl")
  740. if err != nil {
  741. msg = "生成指标代码失败"
  742. return
  743. }
  744. //判断指标名称是否存在
  745. ok, err := CheckDulplicateEdbInfoName(req.EdbName, lang)
  746. if err != nil {
  747. msg = "保存失败"
  748. return
  749. }
  750. if ok {
  751. msg = "指标名称已存在"
  752. err = fmt.Errorf("指标名称已存在")
  753. return
  754. }
  755. source := utils.DATA_SOURCE_CALCULATE_STL
  756. subSource := utils.DATA_SUB_SOURCE_EDB
  757. edbInfo := new(data_manage.EdbInfo)
  758. //获取该层级下最大的排序数
  759. maxSort, err := data.GetEdbClassifyMaxSort(req.ClassifyId, 0)
  760. if err != nil {
  761. msg = "获取失败"
  762. err = fmt.Errorf("获取最大排序失败,Err:" + err.Error())
  763. return
  764. }
  765. edbInfo.EdbCode = edbCode
  766. edbInfo.EdbName = req.EdbName
  767. edbInfo.EdbNameEn = req.EdbName
  768. edbInfo.EdbNameSource = req.EdbName
  769. edbInfo.Frequency = req.Frequency
  770. edbInfo.Unit = req.Unit
  771. edbInfo.UnitEn = req.Unit
  772. edbInfo.CalculateFormula = conf.Config
  773. edbInfo.ClassifyId = req.ClassifyId
  774. edbInfo.SysUserId = adminId
  775. edbInfo.SysUserRealName = adminRealName
  776. edbInfo.CreateTime = time.Now()
  777. edbInfo.ModifyTime = time.Now()
  778. edbInfo.Sort = maxSort + 1
  779. edbInfo.DataDateType = `交易日`
  780. timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
  781. edbInfo.UniqueCode = utils.MD5(utils.DATA_PREFIX + "_" + timestamp)
  782. itemVal, err := data_manage.GetEdbInfoMaxAndMinInfo(source, subSource, edbCode)
  783. if itemVal != nil && err == nil {
  784. edbInfo.MaxValue = itemVal.MaxValue
  785. edbInfo.MinValue = itemVal.MinValue
  786. }
  787. edbInfo.EdbType = 2
  788. edbInfo.Source = source
  789. edbInfo.SubSource = subSource
  790. extra, _ := json.Marshal(req)
  791. edbInfo.Extra = string(extra)
  792. edbInfoId, err := data_manage.AddEdbInfo(edbInfo)
  793. if err != nil {
  794. msg = "保存失败"
  795. err = errors.New("保存失败,Err:" + err.Error())
  796. return
  797. }
  798. edbInfo.EdbInfoId = int(edbInfoId)
  799. var dataList []*stl.EdbDataCalculateStl
  800. for _, v := range edbInfoData {
  801. dataTime, _ := time.Parse(utils.FormatDate, v.DataTime)
  802. dataList = append(dataList, &stl.EdbDataCalculateStl{
  803. EdbInfoId: int(edbInfoId),
  804. EdbCode: edbCode,
  805. DataTime: dataTime,
  806. Value: v.Value,
  807. CreateTime: time.Now(),
  808. ModifyTime: time.Now(),
  809. DataTimestamp: dataTime.UnixMilli(),
  810. })
  811. }
  812. err = indexObj.BatchInsert(dataList)
  813. if err != nil {
  814. msg = "保存失败"
  815. return
  816. }
  817. //保存数据
  818. data_manage.ModifyEdbInfoDataStatus(edbInfoId, source, subSource, edbCode)
  819. maxAndMinItem, _ := data_manage.GetEdbInfoMaxAndMinInfo(source, subSource, edbCode)
  820. if maxAndMinItem != nil {
  821. err = data_manage.ModifyEdbInfoMaxAndMinInfo(int(edbInfoId), maxAndMinItem)
  822. if err != nil {
  823. msg = "保存失败"
  824. err = errors.New("保存失败,Err:" + err.Error())
  825. return
  826. }
  827. }
  828. // 保存配置映射
  829. {
  830. stlMapping := new(stl.CalculateStlConfigMapping)
  831. stlMapping.EdbInfoId = int(edbInfoId)
  832. stlMapping.CalculateStlConfigId = req.CalculateStlConfigId
  833. stlMapping.StlEdbType = req.StlEdbType
  834. stlMapping.CreateTime = time.Now()
  835. stlMapping.ModifyTime = time.Now()
  836. _, err = stlMapping.Insert()
  837. if err != nil {
  838. msg = "保存失败"
  839. err = errors.New("保存配置映射失败,Err:" + err.Error())
  840. return
  841. }
  842. }
  843. // 保存溯源信息
  844. {
  845. fromEdbInfo, er := data_manage.GetEdbInfoById(req.EdbInfoId)
  846. if er != nil {
  847. if er.Error() == utils.ErrNoRow() {
  848. msg = "未找到指标,请刷新后重试"
  849. err = fmt.Errorf("指标不存在,err:" + er.Error())
  850. return
  851. }
  852. msg = "获取失败"
  853. err = er
  854. return
  855. }
  856. edbCalculateMappingInfo := new(data_manage.EdbInfoCalculateMapping)
  857. edbCalculateMappingInfo.EdbInfoId = int(edbInfoId)
  858. edbCalculateMappingInfo.Source = source
  859. edbCalculateMappingInfo.SourceName = "STL趋势分解"
  860. edbCalculateMappingInfo.EdbCode = edbCode
  861. edbCalculateMappingInfo.FromEdbInfoId = req.EdbInfoId
  862. edbCalculateMappingInfo.FromEdbCode = fromEdbInfo.EdbCode
  863. edbCalculateMappingInfo.FromEdbName = fromEdbInfo.EdbName
  864. edbCalculateMappingInfo.FromSource = fromEdbInfo.Source
  865. edbCalculateMappingInfo.FromSourceName = fromEdbInfo.SourceName
  866. edbCalculateMappingInfo.CreateTime = time.Now()
  867. edbCalculateMappingInfo.ModifyTime = time.Now()
  868. err = edbCalculateMappingInfo.Insert()
  869. if err != nil {
  870. msg = "保存失败"
  871. err = errors.New("保存溯源信息失败,Err:" + err.Error())
  872. return
  873. }
  874. }
  875. //添加es
  876. data.AddOrEditEdbInfoToEs(int(edbInfoId))
  877. return
  878. }
  879. func GetStlConfig(edbInfoId int) (resp *response.StlConfigResp, msg string, err error) {
  880. configId, err := stl.GetCalculateStlConfigMappingIdByEdbInfoId(edbInfoId)
  881. if err != nil {
  882. if err.Error() == utils.ErrNoRow() {
  883. msg = "未找到指标信息, 请选择其他指标"
  884. return
  885. }
  886. msg = "查询失败"
  887. return
  888. }
  889. conf, err := stl.GetCalculateStlConfigById(configId)
  890. if err != nil {
  891. if err.Error() == utils.ErrNoRow() {
  892. msg = "未找到配置,请刷新后重试"
  893. return
  894. }
  895. msg = "获取失败"
  896. return
  897. }
  898. var req request.StlConfigReq
  899. if err = json.Unmarshal([]byte(conf.Config), &req); err != nil {
  900. msg = "获取失败"
  901. return
  902. }
  903. edbInfo, err := data_manage.GetEdbInfoById(req.EdbInfoId)
  904. if err != nil {
  905. if err.Error() == utils.ErrNoRow() {
  906. msg = "未找到指标,请刷新后重试"
  907. return
  908. }
  909. msg = "获取失败"
  910. return
  911. }
  912. mappingList, err := stl.GetCalculateStlConfigMappingByConfigId(configId)
  913. if err != nil {
  914. msg = "获取失败"
  915. return
  916. }
  917. stlEdbInfo := make([]*response.StlEdbInfo, 0)
  918. for _, v := range mappingList {
  919. stlEdbInfo = append(stlEdbInfo, &response.StlEdbInfo{
  920. StlEdbType: v.StlEdbType,
  921. EdbInfoId: v.EdbInfoId,
  922. })
  923. }
  924. resp = &response.StlConfigResp{
  925. CalculateStlConfigId: conf.CalculateStlConfigId,
  926. EdbInfoId: req.EdbInfoId,
  927. EdbInfoName: edbInfo.EdbName,
  928. DataRangeType: req.DataRangeType,
  929. StartDate: req.StartDate,
  930. EndDate: req.EndDate,
  931. LastNYear: req.LastNYear,
  932. Period: req.Period,
  933. Seasonal: req.Seasonal,
  934. Trend: req.Trend,
  935. Fraction: req.Fraction,
  936. Robust: req.Robust,
  937. TrendDeg: req.TrendDeg,
  938. SeasonalDeg: req.SeasonalDeg,
  939. LowPassDeg: req.LowPassDeg,
  940. StlEdbInfo: stlEdbInfo,
  941. }
  942. return
  943. }
  944. func CheckDulplicateEdbInfoName(edbName, lang string) (ok bool, err error) {
  945. var count int
  946. var condition string
  947. var pars []interface{}
  948. switch lang {
  949. case utils.EnLangVersion:
  950. condition += " AND edb_name_en = ? "
  951. default:
  952. condition += " AND edb_name=? "
  953. }
  954. pars = append(pars, edbName)
  955. count, err = data_manage.GetEdbInfoCountByCondition(condition, pars)
  956. if err != nil {
  957. return
  958. }
  959. if count > 0 {
  960. ok = true
  961. return
  962. }
  963. return
  964. }