analysis.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. # -*- coding: utf-8 -*-
  2. import numpy as np
  3. from flask import Flask, request
  4. import time
  5. import random
  6. import logging
  7. import traceback
  8. import os
  9. from common.database_dml import get_df_list_from_mongo, insert_data_into_mongo
  10. import plotly.graph_objects as go
  11. import plotly.io as pio
  12. from bson.decimal128 import Decimal128
  13. import numbers
  14. app = Flask('analysis_report——service')
  15. def put_analysis_report_to_html(args, df_predict, df_accuracy):
  16. col_time = args['col_time']
  17. label = args['label']
  18. label_pre = args['label_pre']
  19. farmId = args['farmId']
  20. title = '分析报告 ' + args['mongodb_read_table'].split(',')[1]
  21. acc_flag = df_accuracy.shape[0]
  22. df_predict = df_predict.applymap(
  23. lambda x: float(x.to_decimal()) if isinstance(x, Decimal128) else float(x) if isinstance(x,
  24. numbers.Number) else x).sort_values(
  25. by=col_time)
  26. if acc_flag > 0:
  27. df_accuracy = df_accuracy.applymap(
  28. lambda x: float(x.to_decimal()) if isinstance(x, Decimal128) else float(x) if isinstance(x,
  29. numbers.Number) else x).sort_values(
  30. by=col_time)
  31. # 创建一个图表对象
  32. fig = go.Figure()
  33. # 获取所有的模型
  34. models = df_predict['model'].unique()
  35. # 添加实际功率曲线
  36. fig.add_trace(go.Scatter(
  37. x=df_predict[col_time],
  38. y=df_predict[label],
  39. mode='lines+markers',
  40. name='实际功率', # 实际功率
  41. line=dict(width=1), # 虚线
  42. marker=dict(symbol='circle'),
  43. ))
  44. # 为每个模型添加预测值和实际功率的曲线
  45. for model in models:
  46. # 筛选该模型的数据
  47. model_data = df_predict[df_predict['model'] == model]
  48. # 添加预测值曲线
  49. fig.add_trace(go.Scatter(
  50. x=model_data[col_time],
  51. y=model_data[label_pre],
  52. mode='lines+markers',
  53. name=f'{model} 预测值', # 预测值
  54. marker=dict(symbol='circle'),
  55. line=dict(width=2)
  56. ))
  57. # 设置图表的标题和标签
  58. fig.update_layout(
  59. template='seaborn', # 使用 seaborn 模板
  60. title=dict(
  61. # text=f"{label_pre} 与 {label} 对比", # 标题
  62. x=0.5, font=dict(size=20, color='darkblue') # 标题居中并设置字体大小和颜色
  63. ),
  64. plot_bgcolor='rgba(255, 255, 255, 0.8)', # 背景色
  65. xaxis=dict(
  66. showgrid=True,
  67. gridcolor='rgba(200, 200, 200, 0.5)', # 网格线颜色
  68. title='时间', # 时间轴标题
  69. rangeslider=dict(visible=True), # 显示滚动条
  70. rangeselector=dict(visible=True) # 显示预设的时间范围选择器
  71. ),
  72. yaxis=dict(
  73. showgrid=True,
  74. gridcolor='rgba(200, 200, 200, 0.5)',
  75. title='功率' # y轴标题
  76. ),
  77. legend=dict(
  78. x=0.01,
  79. y=0.99,
  80. bgcolor='rgba(255, 255, 255, 0.7)', # 背景透明
  81. bordercolor='black',
  82. borderwidth=1,
  83. font=dict(size=12) # 字体大小
  84. ),
  85. hovermode='x unified', # 鼠标悬停时显示统一的提示框
  86. hoverlabel=dict(
  87. bgcolor='white',
  88. font_size=14,
  89. font_family="Rockwell", # 设置字体样式
  90. bordercolor='black'
  91. ),
  92. margin=dict(l=50, r=50, t=50, b=50) # 调整边距,避免标题或标签被遮挡
  93. )
  94. # 将折线图保存为 HTML 片段
  95. power_html = pio.to_html(fig, full_html=False)
  96. # -------------------- 准确率表展示--------------------
  97. acc_html = ''
  98. if acc_flag > 0:
  99. acc_html = df_accuracy.sort_values(by=col_time).to_html(classes='table table-bordered table-striped',
  100. index=False)
  101. # -------------------- 准确率汇总展示--------------------
  102. summary_html = ''
  103. if acc_flag > 0:
  104. # 指定需要转换的列
  105. cols_to_convert = ['MAE', 'accuracy', 'RMSE', 'deviationElectricity', 'deviationAssessment']
  106. for col in cols_to_convert:
  107. if col in df_accuracy.columns:
  108. df_accuracy[col] = df_accuracy[col].apply(
  109. lambda x: float(x.to_decimal()) if isinstance(x, Decimal128) else float(x) if isinstance(x,
  110. numbers.Number) else np.nan)
  111. # 确定存在的列
  112. agg_dict = {}
  113. rename_cols = ['model']
  114. if 'MAE' in df_accuracy.columns:
  115. agg_dict['MAE'] = np.nanmean
  116. rename_cols.append('MAE平均值')
  117. if 'accuracy' in df_accuracy.columns:
  118. agg_dict['accuracy'] = np.nanmean
  119. rename_cols.append('准确率平均值')
  120. if 'RMSE' in df_accuracy.columns:
  121. agg_dict['RMSE'] = np.nanmean
  122. rename_cols.append('RMSE平均值')
  123. if 'deviationElectricity' in df_accuracy.columns:
  124. agg_dict['deviationElectricity'] = [np.nanmean, np.nansum]
  125. rename_cols.append('考核电量平均值')
  126. rename_cols.append('考核总电量')
  127. if 'deviationAssessment' in df_accuracy.columns:
  128. agg_dict['deviationAssessment'] = [np.nanmean, np.nansum]
  129. rename_cols.append('考核分数平均值')
  130. rename_cols.append('考核总分数')
  131. if 'qualificationRate' in df_accuracy.columns:
  132. agg_dict['qualificationRate'] = [np.nanmean]
  133. rename_cols.append('合格率平均值')
  134. # 进行分组聚合,如果有需要聚合的列
  135. summary_df = df_accuracy.groupby('model').agg(agg_dict).reset_index()
  136. summary_df.columns = rename_cols
  137. summary_html = summary_df.to_html(classes='table table-bordered table-striped', index=False)
  138. # -------------------- 生成完整 HTML 页面 --------------------
  139. html_content = f"""
  140. <!DOCTYPE html>
  141. <html lang="en">
  142. <head>
  143. <meta charset="UTF-8">
  144. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  145. <title>Data Analysis Report</title>
  146. <!-- 引入 Bootstrap CSS -->
  147. <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  148. <style>
  149. justify-between;{{
  150. display: flex;
  151. justify-content: space-between;
  152. }}
  153. body {{
  154. background-color: #f4f4f9;
  155. font-family: Arial, sans-serif;
  156. padding: 20px;
  157. }}
  158. .container {{
  159. background-color: #fff;
  160. padding: 20px;
  161. border-radius: 10px;
  162. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  163. margin-bottom: 30px;
  164. }}
  165. h1 {{
  166. text-align: center;
  167. color: #333;
  168. margin-bottom: 20px;
  169. }}
  170. .plot-container {{
  171. margin: 20px 0;
  172. max-height: 500px; /* 限制高度 */
  173. overflow-y: auto; /* 显示垂直滚动条 */
  174. }}
  175. .table-container {{
  176. margin-top: 30px;
  177. overflow-x: auto; /* 水平滚动条 */
  178. max-width: 100%; /* 限制宽度 */
  179. white-space: nowrap; /* 防止内容换行 */
  180. max-height: 500px; /* 限制高度 */
  181. overflow-y: auto; /* 显示垂直滚动条 */
  182. }}
  183. .fixed-table thead tr > th:first-child,
  184. .fixed-table tbody tr > td:first-child {{
  185. position: sticky;
  186. left: 0;
  187. z-index: 1;
  188. }}
  189. .fixed-table-header thead tr > th {{
  190. position: sticky;
  191. top: 0;
  192. z-index: 2;
  193. }}
  194. table {{
  195. width: 100%;
  196. font-size: 12px; /* 设置字体大小为12px */
  197. }}
  198. th, td {{
  199. text-align: center; /* 表头和单元格文字居中 */
  200. }}
  201. }}
  202. </style>
  203. </head>
  204. <body>
  205. <div class="container">
  206. <h1>{title}</h1>
  207. <!-- Pandas DataFrame 表格 -->
  208. <div class="plot-container">
  209. <h2>1. 预测功率与实际功率曲线对比</h2>
  210. {power_html}
  211. </div>
  212. <!-- Pandas DataFrame 表格 -->
  213. <div style="display:flex; justify-content: space-between;">
  214. <h2>2. 准确率对比</h2>
  215. <span>
  216. <a href="/formula.xlsx">公式</a>
  217. </span>
  218. </div>
  219. <div class="table-container fixed-table-header">
  220. {acc_html}
  221. </div>
  222. <!-- Pandas DataFrame 表格 -->
  223. <div class="table-container">
  224. <h2>3. 准确率汇总对比</h2>
  225. {summary_html}
  226. </div>
  227. </div>
  228. </body>
  229. </html>
  230. """
  231. filename = f"{farmId}_{int(time.time() * 1000)}_{random.randint(1000, 9999)}.html"
  232. # 保存为 HTML
  233. directory = '/usr/share/nginx/html'
  234. # directory = '../cache'
  235. if not os.path.exists(directory):
  236. os.makedirs(directory)
  237. file_path = os.path.join(directory, filename)
  238. path = f"http://ds1:10010/{filename}"
  239. # 将 HTML 内容写入文件
  240. with open(file_path, "w", encoding="utf-8") as f:
  241. f.write(html_content)
  242. print("HTML report generated successfully!")
  243. return path
  244. @app.route('/analysis_report_small', methods=['POST'])
  245. def analysis_report():
  246. start_time = time.time()
  247. result = {}
  248. success = 0
  249. path = ""
  250. print("Program starts execution!")
  251. try:
  252. args = request.values.to_dict()
  253. print('args', args)
  254. logger.info(args)
  255. # 获取数据
  256. df_predict, df_accuracy = get_df_list_from_mongo(args)[0], get_df_list_from_mongo(args)[1]
  257. path = put_analysis_report_to_html(args, df_predict, df_accuracy)
  258. success = 1
  259. except Exception as e:
  260. my_exception = traceback.format_exc()
  261. my_exception.replace("\n", "\t")
  262. result['msg'] = my_exception
  263. end_time = time.time()
  264. result['success'] = success
  265. result['args'] = args
  266. result['start_time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(start_time))
  267. result['end_time'] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(end_time))
  268. result['file_path'] = path
  269. print("Program execution ends!")
  270. return result
  271. if __name__ == "__main__":
  272. print("Program starts execution!")
  273. logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  274. logger = logging.getLogger("analysis_report log")
  275. from waitress import serve
  276. serve(app, host="0.0.0.0", port=10099)
  277. print("server start!")