Ken-INOUE commited on
Commit
46ca6fa
·
1 Parent(s): 3934bd9

Refactor app.py to integrate threshold diagnosis and trend detection features, enhancing Gradio UI for improved user experience.

Browse files
Files changed (1) hide show
  1. app.py +136 -44
app.py CHANGED
@@ -1,11 +1,13 @@
1
- # app.py
 
2
  import gradio as gr
3
  import pandas as pd
4
  import numpy as np
 
5
  import json
6
  import os
7
 
8
- # --- 共通ユーティリティ ---
9
  def judge_status(value, ll, l, h, hh):
10
  if pd.notna(ll) and value < ll:
11
  return "LOW-LOW"
@@ -23,23 +25,8 @@ def convert_value(v):
23
  return v.item()
24
  return float(v) if isinstance(v, (np.floating, float)) else int(v) if isinstance(v, (np.integer, int)) else v
25
 
26
- # --- 閾値診断(本物処理 ---
27
- def diagnose_process_range(csv_file, excel_file, process_name, datetime_str, window_minutes):
28
- try:
29
- df = pd.read_csv(csv_file.name, header=[0, 1, 2])
30
- timestamp_col = df.iloc[:, 0]
31
- df = df.drop(df.columns[0], axis=1)
32
- df.insert(0, "timestamp", timestamp_col)
33
- df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
34
-
35
- thresholds_df = pd.read_excel(excel_file.name)
36
- thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE": True, "FALSE": False})
37
- for col in ["LL", "L", "H", "HH"]:
38
- if col in thresholds_df.columns:
39
- thresholds_df[col] = pd.to_numeric(thresholds_df[col], errors="coerce")
40
- except Exception as e:
41
- return None, None, None, f"❌ 入力ファイルの読み込みに失敗しました: {e}", None
42
-
43
  try:
44
  target_time = pd.to_datetime(datetime_str)
45
  except Exception:
@@ -76,7 +63,11 @@ def diagnose_process_range(csv_file, excel_file, process_name, datetime_str, win
76
  ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
77
  )
78
  status_ratio = (status_counts / total * 100).round(1)
79
- result_df_all = pd.DataFrame({"状態": status_counts.index, "件数": status_counts.values, "割合(%)": status_ratio.values})
 
 
 
 
80
 
81
  important_results = [r for r in all_results if r["重要項目"]]
82
  if important_results:
@@ -85,7 +76,11 @@ def diagnose_process_range(csv_file, excel_file, process_name, datetime_str, win
85
  ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
86
  )
87
  status_ratio_imp = (status_counts_imp / total_imp * 100).round(1)
88
- result_df_imp = pd.DataFrame({"状態": status_counts_imp.index, "件数": status_counts_imp.values, "割合(%)": status_ratio_imp.values})
 
 
 
 
89
  else:
90
  result_df_imp = pd.DataFrame(columns=["状態", "件数", "割合(%)"])
91
  status_ratio_imp = pd.Series(dtype=float)
@@ -126,32 +121,122 @@ def diagnose_process_range(csv_file, excel_file, process_name, datetime_str, win
126
 
127
  return result_df_all, result_df_imp, result_df_imp_items, summary, result_json
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- # --- 傾向検出(ダミー) ---
131
- def dummy_trend(csv_file, excel_file, process_name, datetime_str, window_minutes, forecast_minutes):
132
- return pd.DataFrame([{"ItemName": "ダミー項目", "傾向": "安定"}]), "ダミー傾向", {"dummy": True}
133
 
134
- # --- 予兆解析(ダミー) ---
135
- def dummy_forecast(csv_file, excel_file, process_name, datetime_str, forecast_minutes):
136
- return pd.DataFrame([{"ItemName": "ダミー項目", "予測": "OK"}]), "✅ ダミー予兆", {"dummy": True}
137
 
 
 
 
138
 
139
- # --- Gradio UI (統合ム Step 2) ---
140
- with gr.Blocks(css="body {overflow-y: auto !important;}") as demo:
141
- gr.Markdown("## 統合解析ツール (Step 2: 閾値診断のみ本物処理)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  with gr.Tabs():
144
- # Tab1: 閾値診断
145
  with gr.Tab("閾値診断"):
146
- with gr.Row():
147
- csv_input = gr.File(label="CSVファイル", file_types=[".csv"], type="filepath")
148
- excel_input = gr.File(label="Excel閾値ファイル", file_types=[".xlsx"], type="filepath")
149
-
150
  process_name = gr.Textbox(label="プロセス名", value="E018-A012_除害RO")
151
  datetime_str = gr.Textbox(label="診断基準日時", value="2025/8/1 1:05")
152
  window_minutes = gr.Number(label="さかのぼる時間幅(分)", value=60)
153
  run_btn = gr.Button("診断を実行")
154
-
155
  result_df_all = gr.Dataframe(label="全項目の状態集計結果")
156
  result_df_imp = gr.Dataframe(label="重要項目全体の状態集計結果")
157
  result_df_imp_items = gr.Dataframe(label="重要項目ごとの状態集計結果")
@@ -160,21 +245,28 @@ with gr.Blocks(css="body {overflow-y: auto !important;}") as demo:
160
 
161
  run_btn.click(
162
  diagnose_process_range,
163
- inputs=[csv_input, excel_input, process_name, datetime_str, window_minutes],
164
  outputs=[result_df_all, result_df_imp, result_df_imp_items, summary_output, json_output]
165
  )
166
 
167
- # Tab2: 傾向検出
168
  with gr.Tab("傾向検出"):
169
- gr.Markdown("ここはまだダミーです。")
 
 
 
 
 
 
 
170
 
171
- # Tab3: 予兆解析
172
- with gr.Tab("予兆解析"):
173
- gr.Markdown("ここはまだダミーです。")
 
 
174
 
175
- # --- 実行モード選択 ---
176
  if __name__ == "__main__":
177
- use_mcp = os.getenv("USE_MCP", "0") == "1"
178
  if use_mcp:
179
  demo.launch(mcp_server=True)
180
  else:
 
1
+ # 統合版: 閾値診断 + 傾向検出 (CSV/Excel共通入力, MCP対応)
2
+
3
  import gradio as gr
4
  import pandas as pd
5
  import numpy as np
6
+ from sklearn.linear_model import LinearRegression
7
  import json
8
  import os
9
 
10
+ # ===== 共通ユーティリティ =====
11
  def judge_status(value, ll, l, h, hh):
12
  if pd.notna(ll) and value < ll:
13
  return "LOW-LOW"
 
25
  return v.item()
26
  return float(v) if isinstance(v, (np.floating, float)) else int(v) if isinstance(v, (np.integer, int)) else v
27
 
28
+ # ===== 閾値診断処理 =====
29
+ def diagnose_process_range(df, thresholds_df, process_name, datetime_str, window_minutes):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  try:
31
  target_time = pd.to_datetime(datetime_str)
32
  except Exception:
 
63
  ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
64
  )
65
  status_ratio = (status_counts / total * 100).round(1)
66
+ result_df_all = pd.DataFrame({
67
+ "状態": status_counts.index,
68
+ "件数": status_counts.values,
69
+ "割合(%)": status_ratio.values
70
+ })
71
 
72
  important_results = [r for r in all_results if r["重要項目"]]
73
  if important_results:
 
76
  ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
77
  )
78
  status_ratio_imp = (status_counts_imp / total_imp * 100).round(1)
79
+ result_df_imp = pd.DataFrame({
80
+ "状態": status_counts_imp.index,
81
+ "件数": status_counts_imp.values,
82
+ "割合(%)": status_ratio_imp.values
83
+ })
84
  else:
85
  result_df_imp = pd.DataFrame(columns=["状態", "件数", "割合(%)"])
86
  status_ratio_imp = pd.Series(dtype=float)
 
121
 
122
  return result_df_all, result_df_imp, result_df_imp_items, summary, result_json
123
 
124
+ # ===== 傾向検出処理 =====
125
+ def detect_trends_with_forecast(df, thresholds_df, process_name, datetime_str, window_minutes, forecast_minutes):
126
+ target_time = pd.to_datetime(datetime_str)
127
+ start_time = target_time - pd.Timedelta(minutes=window_minutes)
128
+ end_time = target_time
129
+ df_window = df[(df["timestamp"] >= start_time) & (df["timestamp"] <= end_time)]
130
+ if df_window.empty:
131
+ return None, "⚠ 指定時間幅にデータなし", None
132
+
133
+ interval = df_window["timestamp"].diff().median()
134
+ if pd.isna(interval):
135
+ return None, "⚠ サンプリング間隔を検出できません", None
136
+ interval_minutes = interval.total_seconds() / 60
137
+
138
+ proc_thresholds = thresholds_df[(thresholds_df["ProcessNo_ProcessName"] == process_name) &
139
+ (thresholds_df["Important"] == True)]
140
+ if proc_thresholds.empty:
141
+ return None, f"⚠ プロセス {process_name} の重要項目なし", None
142
+
143
+ results = []
144
+ for _, thr in proc_thresholds.iterrows():
145
+ col_tuple = (thr["ColumnID"], thr["ItemName"], thr["ProcessNo_ProcessName"])
146
+ if col_tuple not in df.columns:
147
+ continue
148
+ series = df_window[col_tuple].dropna()
149
+ if len(series) < 3:
150
+ continue
151
+
152
+ x = np.arange(len(series)).reshape(-1, 1)
153
+ y = series.values.reshape(-1, 1)
154
+ model = LinearRegression().fit(x, y)
155
+ slope = model.coef_[0][0]
156
+
157
+ last_val = series.iloc[-1]
158
+ n = len(series)
159
+
160
+ forecast_steps = int(forecast_minutes / interval_minutes)
161
+ forecast_index = n + forecast_steps
162
+ forecast_val = model.predict([[forecast_index]])[0][0]
163
+ forecast_time = target_time + pd.Timedelta(minutes=forecast_minutes)
164
+
165
+ l, ll, h, hh = thr.get("L"), thr.get("LL"), thr.get("H"), thr.get("HH")
166
+
167
+ status = "安定"
168
+ if slope < 0 and pd.notna(ll):
169
+ if last_val > ll:
170
+ status = "LL接近下降傾向"
171
+ elif last_val <= ll:
172
+ status = "LL逸脱下降傾向"
173
+ if slope > 0 and pd.notna(hh):
174
+ if last_val < hh:
175
+ status = "HH接近上昇傾向"
176
+ elif last_val >= hh:
177
+ status = "HH逸脱上昇傾向"
178
+
179
+ forecast_status = "安定"
180
+ if pd.notna(ll) and forecast_val <= ll:
181
+ forecast_status = "LL逸脱予測"
182
+ elif pd.notna(hh) and forecast_val >= hh:
183
+ forecast_status = "HH逸脱予測"
184
+
185
+ results.append({
186
+ "ItemName": thr["ItemName"],
187
+ "傾向": status,
188
+ "傾き": round(slope, 4),
189
+ "最終値": round(float(last_val), 3) if pd.notna(last_val) else None,
190
+ "予測値": round(float(forecast_val), 3),
191
+ "予測時刻": str(forecast_time),
192
+ "予測傾向": forecast_status,
193
+ "サンプリング間隔(分)": interval_minutes,
194
+ "LL": ll, "L": l, "H": h, "HH": hh
195
+ })
196
 
197
+ result_df = pd.DataFrame(results)
198
+ result_json = json.dumps(results, ensure_ascii=False, indent=2)
199
+ return result_df, "✅ 傾向検出+未来予測完了", result_json
200
 
201
+ # ===== Gradio UI =====
202
+ with gr.Blocks(css=".gradio-container {overflow:auto !important}") as demo:
203
+ gr.Markdown("## 統合版アプリ (閾値診断 + 傾向検出)")
204
 
205
+ with gr.Row():
206
+ csv_input = gr.File(label="CSVファイルをアップロード", file_types=[".csv"], type="filepath")
207
+ excel_input = gr.File(label="Excel閾値ファイルをアップロード", file_types=[".xlsx"], type="filepath")
208
 
209
+ # ファイルを保持するステ
210
+ state_df = gr.State()
211
+ state_thresholds = gr.State()
212
+
213
+ def load_files(csv_file, excel_file):
214
+ try:
215
+ df = pd.read_csv(csv_file.name, header=[0,1,2])
216
+ timestamp_col = df.iloc[:,0]
217
+ df = df.drop(df.columns[0], axis=1)
218
+ df.insert(0,"timestamp", timestamp_col)
219
+ df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
220
+ thresholds_df = pd.read_excel(excel_file.name)
221
+ thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE":True,"FALSE":False})
222
+ for col in ["LL","L","H","HH"]:
223
+ if col in thresholds_df.columns:
224
+ thresholds_df[col] = pd.to_numeric(thresholds_df[col], errors="coerce")
225
+ return df, thresholds_df, "✅ ファイルを読み込みました"
226
+ except Exception as e:
227
+ return None, None, f"❌ 読み込み失敗: {e}"
228
+
229
+ load_btn = gr.Button("ファイルを読み込み")
230
+ load_status = gr.Textbox(label="ロード状態")
231
+
232
+ load_btn.click(load_files, inputs=[csv_input, excel_input], outputs=[state_df, state_thresholds, load_status])
233
 
234
  with gr.Tabs():
 
235
  with gr.Tab("閾値診断"):
 
 
 
 
236
  process_name = gr.Textbox(label="プロセス名", value="E018-A012_除害RO")
237
  datetime_str = gr.Textbox(label="診断基準日時", value="2025/8/1 1:05")
238
  window_minutes = gr.Number(label="さかのぼる時間幅(分)", value=60)
239
  run_btn = gr.Button("診断を実行")
 
240
  result_df_all = gr.Dataframe(label="全項目の状態集計結果")
241
  result_df_imp = gr.Dataframe(label="重要項目全体の状態集計結果")
242
  result_df_imp_items = gr.Dataframe(label="重要項目ごとの状態集計結果")
 
245
 
246
  run_btn.click(
247
  diagnose_process_range,
248
+ inputs=[state_df, state_thresholds, process_name, datetime_str, window_minutes],
249
  outputs=[result_df_all, result_df_imp, result_df_imp_items, summary_output, json_output]
250
  )
251
 
 
252
  with gr.Tab("傾向検出"):
253
+ process_name2 = gr.Textbox(label="プロセス名", value="E018-A012_除害RO")
254
+ datetime_str2 = gr.Textbox(label="基準日時", value="2025/8/1 1:05")
255
+ window_minutes2 = gr.Number(label="さかのぼる時間幅(分)", value=60)
256
+ forecast_minutes2 = gr.Number(label="未来予測時間幅(分)", value=60)
257
+ run_btn2 = gr.Button("傾向検出を実行")
258
+ result_df2 = gr.Dataframe(label="傾向+未来予測結果")
259
+ summary_output2 = gr.Textbox(label="サマリー")
260
+ json_output2 = gr.Json(label="JSON結果")
261
 
262
+ run_btn2.click(
263
+ detect_trends_with_forecast,
264
+ inputs=[state_df, state_thresholds, process_name2, datetime_str2, window_minutes2, forecast_minutes2],
265
+ outputs=[result_df2, summary_output2, json_output2]
266
+ )
267
 
 
268
  if __name__ == "__main__":
269
+ use_mcp = os.getenv("USE_MCP","0") == "1"
270
  if use_mcp:
271
  demo.launch(mcp_server=True)
272
  else: