Ken-INOUE commited on
Commit
73596a8
·
1 Parent(s): d7bc1e2

Enhance trend detection functionality by integrating future forecasting and improving error handling. Update Gradio UI for clearer input prompts and output display.

Browse files
Files changed (1) hide show
  1. app.py +122 -91
app.py CHANGED
@@ -1,122 +1,153 @@
1
- # 傾向検出アプリ(Gradio 単独版)
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
 
9
- # ===============================
10
- # データ読み込み関数
11
- # ===============================
12
- def load_csv(file):
13
  try:
14
- df = pd.read_csv(file.name, header=[0, 1, 2])
 
15
  timestamp_col = df.iloc[:, 0]
16
  df = df.drop(df.columns[0], axis=1)
17
- df.insert(0, "timestamp", pd.to_datetime(timestamp_col, errors="coerce"))
18
- return df, f"✅ CSVを読み込みました({df.shape[0]}行 × {df.shape[1]}列)"
19
- except Exception as e:
20
- return None, f"❌ 読み込みエラー: {str(e)}"
21
 
22
- # ===============================
23
- # 傾向検出関数
24
- # ===============================
25
- def detect_trend(csv_file, process_name, datetime_str, window_minutes=60):
26
- # データ読み込み
27
- df, msg = load_csv(csv_file)
28
- if df is None:
29
- return msg, None, None, None
30
 
31
- try:
32
  target_time = pd.to_datetime(datetime_str)
33
- except Exception:
34
- return f"⚠ 入力した日時 {datetime_str} が無効です。", None, None, None
35
-
36
- start_time = target_time - pd.Timedelta(minutes=window_minutes)
37
- df_window = df[(df["timestamp"] >= start_time) & (df["timestamp"] <= target_time)]
38
-
39
- if df_window.empty:
40
- return f"⚠ 指定した時幅にデータが見つかりません。", None, None, None
41
-
42
- # 対象プロセスのカラムを抽出
43
- proc_cols = [col for col in df_window.columns if isinstance(col, tuple) and col[2] == process_name]
44
- if not proc_cols:
45
- return f"⚠ プロセス {process_name} のカラムが見つかりません。", None, None, None
46
-
47
- results = []
48
- for col in proc_cols:
49
- series = df_window[col].dropna()
50
- if len(series) < 3:
51
- continue
52
-
53
- X = np.arange(len(series)).reshape(-1, 1)
54
- y = series.values
55
- model = LinearRegression().fit(X, y)
56
- slope = model.coef_[0]
57
-
58
- trend = "上昇" if slope > 0 else "下降" if slope < 0 else "横ばい"
59
- results.append({"ItemName": col[1], "傾き": slope, "傾向": trend})
60
-
61
- if not results:
62
- return f"⚠ 傾向を検出できるデータがありません。", None, None, None
63
-
64
- df_results = pd.DataFrame(results)
65
-
66
- # サマリー
67
- trend_counts = df_results["傾向"].value_counts().to_dict()
68
- summary = f"✅ {process_name} の傾向検出完了({start_time} ~ {target_time})\n"
69
- summary += " / ".join([f"{k}:{v}" for k, v in trend_counts.items()])
70
-
71
- # JSON
72
- result_json = json.dumps(results, ensure_ascii=False, indent=2)
73
-
74
- return "✅ 診断完了", df_results, summary, result_json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
 
76
 
77
- # ===============================
78
- # Gradio アプリ UI
79
- # ===============================
80
- custom_css = """
81
- body, html, #root, [data-testid="block-container"] {
82
- height: auto !important;
83
- overflow-y: auto !important;
84
- }
85
- """
86
 
87
- with gr.Blocks(css=custom_css) as demo:
88
- gr.Markdown("## 📈 傾向検出アプリ")
 
89
 
90
  with gr.Row():
91
  csv_input = gr.File(label="CSVファイルをアップロード", type="filepath")
92
- process_input = gr.Textbox(label="プロセス名 (例: E018-A012_除害RO)")
93
- datetime_input = gr.Textbox(label="診断基準日時 (例: 2025/8/1 1:05)")
94
- window_input = gr.Number(label="時間幅 (分)", value=60)
95
-
96
- run_button = gr.Button("傾向検出を実行")
97
 
98
  with gr.Row():
99
- msg_output = gr.Textbox(label="メッージ")
 
100
  with gr.Row():
101
- table_output = gr.Dataframe(label="傾向検出結果", wrap=True)
102
- with gr.Row():
103
- summary_output = gr.Textbox(label="サマリー")
104
- with gr.Row():
105
- json_output = gr.JSON(label="JSON出力")
106
 
107
- run_button.click(
108
- detect_trend,
109
- inputs=[csv_input, process_input, datetime_input, window_input],
110
- outputs=[msg_output, table_output, summary_output, json_output]
 
 
 
 
 
 
 
111
  )
112
 
113
- # ===============================
114
- # 実行
115
- # ===============================
116
  if __name__ == "__main__":
117
  import os
118
  use_mcp = os.getenv("USE_MCP", "0") == "1"
119
  if use_mcp:
120
  demo.launch(server_name="0.0.0.0", mcp_server=True)
121
  else:
122
- demo.launch(server_name="0.0.0.0", share=False)
 
1
+ # 傾向検出アプリ(Gradio版)
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 io
9
 
10
+ # --- 状態判定&未来予測関数 ---
11
+ def detect_trends_with_forecast(process_name, datetime_str, window_minutes, forecast_minutes, csv_file, excel_file):
 
 
12
  try:
13
+ # CSV読み込み(3行ヘッダー)
14
+ df = pd.read_csv(csv_file.name, header=[0, 1, 2])
15
  timestamp_col = df.iloc[:, 0]
16
  df = df.drop(df.columns[0], axis=1)
17
+ df.insert(0, "timestamp", timestamp_col)
18
+ df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
 
 
19
 
20
+ # 閾値テーブル読み込み
21
+ thresholds_df = pd.read_excel(excel_file.name)
22
+ thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE": True, "FALSE": False})
23
+ for col in ["LL", "L", "H", "HH"]:
24
+ if col in thresholds_df.columns:
25
+ thresholds_df[col] = pd.to_numeric(thresholds_df[col], errors="coerce")
 
 
26
 
27
+ # --- 時間範囲 ---
28
  target_time = pd.to_datetime(datetime_str)
29
+ start_time = target_time - pd.Timedelta(minutes=window_minutes)
30
+ end_time = target_time
31
+ df_window = df[(df["timestamp"] >= start_time) & (df["timestamp"] <= end_time)]
32
+
33
+ if df_window.empty:
34
+ return None, "⚠ 指定時間幅にデータなし", None
35
+
36
+ # サンプリング
37
+ interval = df_window["timestamp"].diff().median()
38
+ if pd.isna(interval):
39
+ return None, "⚠ サンプリング間隔を検出できません", None
40
+ interval_minutes = interval.total_seconds() / 60
41
+
42
+ # 閾値:対象プロセスかつ重要項目
43
+ proc_thresholds = thresholds_df[(thresholds_df["ProcessNo_ProcessName"] == process_name) &
44
+ (thresholds_df["Important"] == True)]
45
+ if proc_thresholds.empty:
46
+ return None, f"⚠ プロセス {process_name} の重要項目なし", None
47
+
48
+ results = []
49
+ for _, thr in proc_thresholds.iterrows():
50
+ col_tuple = (thr["ColumnID"], thr["ItemName"], thr["ProcessNo_ProcessName"])
51
+ if col_tuple not in df.columns:
52
+ continue
53
+
54
+ series = df_window[col_tuple].dropna()
55
+ if len(series) < 3:
56
+ continue
57
+
58
+ # 線形回帰
59
+ x = np.arange(len(series)).reshape(-1, 1)
60
+ y = series.values.reshape(-1, 1)
61
+ model = LinearRegression().fit(x, y)
62
+ slope = model.coef_[0][0]
63
+
64
+ last_val = series.iloc[-1]
65
+ n = len(series)
66
+
67
+ # 未来予測
68
+ forecast_steps = int(forecast_minutes / interval_minutes)
69
+ forecast_index = n + forecast_steps
70
+ forecast_val = model.predict([[forecast_index]])[0][0]
71
+ forecast_time = target_time + pd.Timedelta(minutes=forecast_minutes)
72
+
73
+ l, ll, h, hh = thr.get("L"), thr.get("LL"), thr.get("H"), thr.get("HH")
74
+
75
+ # 現在の傾向
76
+ status = "安定"
77
+ if slope < 0 and pd.notna(ll):
78
+ if last_val > ll:
79
+ status = "LL接近下降傾向"
80
+ elif last_val <= ll:
81
+ status = "LL逸脱下降傾向"
82
+ if slope > 0 and pd.notna(hh):
83
+ if last_val < hh:
84
+ status = "HH接近上昇傾向"
85
+ elif last_val >= hh:
86
+ status = "HH逸脱上昇傾向"
87
+
88
+ # 未来予測の逸脱
89
+ forecast_status = "安定"
90
+ if pd.notna(ll) and forecast_val <= ll:
91
+ forecast_status = "LL逸脱予測"
92
+ elif pd.notna(hh) and forecast_val >= hh:
93
+ forecast_status = "HH逸脱予測"
94
+
95
+ results.append({
96
+ "ItemName": thr["ItemName"],
97
+ "傾向": status,
98
+ "傾き": round(slope, 4),
99
+ "最終値": round(float(last_val), 3) if pd.notna(last_val) else None,
100
+ "予測値": round(float(forecast_val), 3),
101
+ "予測時刻": str(forecast_time),
102
+ "予測傾向": forecast_status,
103
+ "サンプリング間隔(分)": interval_minutes,
104
+ "LL": ll, "L": l, "H": h, "HH": hh
105
+ })
106
+
107
+ result_df = pd.DataFrame(results)
108
+ result_json = json.dumps(results, ensure_ascii=False, indent=2)
109
+
110
+ # JSONをファイルとして出力
111
+ json_bytes = io.BytesIO(result_json.encode("utf-8"))
112
+
113
+ return result_df, "✅ 傾向検出+未来予測完了", json_bytes
114
 
115
+ except Exception as e:
116
+ return None, f"❌ エラー: {str(e)}", None
117
 
 
 
 
 
 
 
 
 
 
118
 
119
+ # --- Gradio UI ---
120
+ with gr.Blocks() as demo:
121
+ gr.Markdown("## 傾向検出アプリ(重要項目ごとの詳細+閾値予測)")
122
 
123
  with gr.Row():
124
  csv_input = gr.File(label="CSVファイルをアップロード", type="filepath")
125
+ excel_input = gr.File(label="閾値テーブル (Excel)", type="filepath")
 
 
 
 
126
 
127
  with gr.Row():
128
+ process_name = gr.Textbox(label="プロス名", placeholder="例: E018-A012_除害RO")
129
+ datetime_str = gr.Textbox(label="日時 (例: 2025/8/1 1:05)")
130
  with gr.Row():
131
+ window_minutes = gr.Number(label="参照時間幅(分)", value=60)
132
+ forecast_minutes = gr.Number(label="未来予測時間幅(分)", value=60)
 
 
 
133
 
134
+ run_btn = gr.Button("実行")
135
+
136
+ with gr.Row():
137
+ result_table = gr.Dataframe(label="傾向+未来予測結果")
138
+ summary_output = gr.Textbox(label="サマリー")
139
+ json_download = gr.File(label="JSON結果ダウンロード")
140
+
141
+ run_btn.click(
142
+ fn=detect_trends_with_forecast,
143
+ inputs=[process_name, datetime_str, window_minutes, forecast_minutes, csv_input, excel_input],
144
+ outputs=[result_table, summary_output, json_download]
145
  )
146
 
 
 
 
147
  if __name__ == "__main__":
148
  import os
149
  use_mcp = os.getenv("USE_MCP", "0") == "1"
150
  if use_mcp:
151
  demo.launch(server_name="0.0.0.0", mcp_server=True)
152
  else:
153
+ demo.launch(server_name="0.0.0.0")