Ken-INOUE commited on
Commit
bac52f7
·
1 Parent(s): 96513e9

Initial implementation of the project structure and core functionality.

Browse files
Files changed (1) hide show
  1. app.py +186 -0
app.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 予兆解析アプリ Gradio + 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 normalize(s):
12
+ return str(s).replace("\u3000", " ").replace("\n", "").replace("\r", "").strip()
13
+
14
+ def find_matching_column(df, col_id, item_name, process_name):
15
+ norm_item = normalize(item_name)
16
+ candidates = [
17
+ c for c in df.columns
18
+ if isinstance(c, str)
19
+ and col_id in c
20
+ and process_name in c
21
+ and norm_item in normalize(c)
22
+ ]
23
+ return candidates[0] if candidates else None
24
+
25
+ # --- 予兆解析関数 ---
26
+ def forecast_process_with_lag(csv_file, excel_file, lag_file, process_name, datetime_str, forecast_minutes):
27
+ try:
28
+ # CSV 読み込み(3行ヘッダー)
29
+ df = pd.read_csv(csv_file.name, header=[0, 1, 2])
30
+ timestamp_col = pd.to_datetime(df.iloc[:, 0], errors="coerce")
31
+ df = df.drop(df.columns[0], axis=1)
32
+ df.insert(0, "timestamp", timestamp_col)
33
+
34
+ # MultiIndex → 文字列化
35
+ def col_to_str(col):
36
+ return "_".join([str(c) for c in col if c]) if isinstance(col, tuple) else str(col)
37
+ df.columns = [
38
+ "timestamp" if (isinstance(c, str) and c == "timestamp") else col_to_str(c)
39
+ for c in df.columns
40
+ ]
41
+
42
+ # 閾値テーブル
43
+ thresholds_df = pd.read_excel(excel_file.name)
44
+ thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE": True, "FALSE": False})
45
+ for col in ["LL", "L", "H", "HH"]:
46
+ if col in thresholds_df.columns:
47
+ thresholds_df[col] = pd.to_numeric(thresholds_df[col], errors="coerce")
48
+
49
+ # ラグテーブル
50
+ lag_matrix = pd.read_excel(lag_file.name, index_col=0)
51
+
52
+ except Exception as e:
53
+ return None, f"❌ 入力ファイルの読み込みに失敗しました: {e}", None
54
+
55
+ try:
56
+ target_time = pd.to_datetime(datetime_str)
57
+ forecast_time = target_time + pd.Timedelta(minutes=forecast_minutes)
58
+ except Exception:
59
+ return None, f"⚠ 入力した日時 {datetime_str} が無効です。", None
60
+
61
+ proc_thresholds = thresholds_df[(thresholds_df["ProcessNo_ProcessName"] == process_name) & (thresholds_df["Important"] == True)]
62
+ if proc_thresholds.empty:
63
+ return None, f"⚠ プロセス {process_name} に重要項目なし", None
64
+
65
+ if process_name not in lag_matrix.index:
66
+ return None, f"⚠ タイムラグ表に {process_name} の行がありません", None
67
+
68
+ lag_row = lag_matrix.loc[process_name].dropna()
69
+ lag_row = lag_row[lag_row > 0] # 正のラグのみ
70
+ if lag_row.empty:
71
+ return None, f"⚠ プロセス {process_name} に正のラグを持つ上流工程がありません", None
72
+
73
+ results = []
74
+ for _, thr in proc_thresholds.iterrows():
75
+ y_col = find_matching_column(df, thr["ColumnID"], thr["ItemName"], thr["ProcessNo_ProcessName"])
76
+ if y_col is None:
77
+ continue
78
+
79
+ # 学習データ(直近24時間)
80
+ df_window = df[df["timestamp"] <= target_time].copy()
81
+ df_window = df_window[df_window["timestamp"] >= target_time - pd.Timedelta(hours=24)]
82
+ if df_window.empty:
83
+ continue
84
+
85
+ try:
86
+ base_df = df_window[["timestamp", y_col]].rename(columns={y_col: "y"})
87
+ except KeyError:
88
+ continue
89
+
90
+ merged_df = base_df.copy()
91
+ for up_proc, lag_min in lag_row.items():
92
+ try:
93
+ up_cols = [c for c in df.columns if isinstance(c, str) and up_proc in c]
94
+ for x_col in up_cols:
95
+ shifted = df_window.loc[:, ["timestamp", x_col]].copy()
96
+ shifted["timestamp"] = shifted["timestamp"] + pd.Timedelta(minutes=lag_min)
97
+ shifted = shifted.rename(columns={x_col: f"{x_col}_lag{lag_min}"})
98
+ merged_df = pd.merge_asof(
99
+ merged_df.sort_values("timestamp"),
100
+ shifted.sort_values("timestamp"),
101
+ on="timestamp",
102
+ direction="nearest"
103
+ )
104
+ except Exception:
105
+ continue
106
+
107
+ X_all = merged_df.drop(columns=["timestamp", "y"], errors="ignore").values
108
+ Y_all = merged_df["y"].values
109
+ if X_all.shape[1] == 0 or len(Y_all) < 5:
110
+ continue
111
+
112
+ # モデル学習
113
+ model = LinearRegression().fit(X_all, Y_all)
114
+
115
+ # 未来予測
116
+ X_pred = []
117
+ for up_proc, lag_min in lag_row.items():
118
+ up_cols = [c for c in df.columns if isinstance(c, str) and up_proc in c]
119
+ for x_col in up_cols:
120
+ try:
121
+ ref_time = forecast_time - pd.Timedelta(minutes=lag_min)
122
+ idx = (df["timestamp"] - ref_time).abs().idxmin()
123
+ X_pred.append(df.loc[idx, x_col])
124
+ except Exception:
125
+ continue
126
+ if not X_pred:
127
+ continue
128
+
129
+ pred_val = model.predict([X_pred])[0]
130
+
131
+ # 閾値リスク判定
132
+ ll, l, h, hh = thr.get("LL"), thr.get("L"), thr.get("H"), thr.get("HH")
133
+ risk = "OK"
134
+ if pd.notna(ll) and pred_val <= ll:
135
+ risk = "LOW-LOW"
136
+ elif pd.notna(l) and pred_val <= l:
137
+ risk = "LOW"
138
+ elif pd.notna(hh) and pred_val >= hh:
139
+ risk = "HIGH-HIGH"
140
+ elif pd.notna(h) and pred_val >= h:
141
+ risk = "HIGH"
142
+
143
+ results.append({
144
+ "ItemName": thr["ItemName"],
145
+ "予測値": round(float(pred_val), 3),
146
+ "予測時刻": str(forecast_time),
147
+ "予測リスク": risk,
148
+ "使用上流工程数": len(lag_row)
149
+ })
150
+
151
+ result_df = pd.DataFrame(results)
152
+ result_json = json.dumps(results, ensure_ascii=False, indent=2)
153
+ summary = f"✅ {process_name} の予兆解析完了 ({target_time} → {forecast_time})"
154
+ return result_df, summary, result_json
155
+
156
+ # --- Gradio UI ---
157
+ with gr.Blocks(css="body {overflow-y: scroll;}") as demo:
158
+ gr.Markdown("## 予兆解析アプリ (MCP対応)")
159
+
160
+ with gr.Row():
161
+ csv_input = gr.File(label="CSVファイルをアップロード", file_types=[".csv"], type="filepath")
162
+ excel_input = gr.File(label="Excel閾値ファイルをアップロード", file_types=[".xlsx"], type="filepath")
163
+ lag_input = gr.File(label="タイムラグファイルをアップロード", file_types=[".xlsx"], type="filepath")
164
+
165
+ process_name = gr.Textbox(label="プロセス名", value="E018-A012_除害RO")
166
+ datetime_str = gr.Textbox(label="基準日時", value="2025/8/2 0:05")
167
+ forecast_minutes = gr.Number(label="予測時間幅(分)", value=60)
168
+
169
+ run_btn = gr.Button("予兆解析を実行")
170
+
171
+ result_df = gr.Dataframe(label="予兆解析結果", wrap=True, interactive=False)
172
+ summary_output = gr.Textbox(label="サマリー")
173
+ json_output = gr.Json(label="JSON結果")
174
+
175
+ run_btn.click(
176
+ forecast_process_with_lag,
177
+ inputs=[csv_input, excel_input, lag_input, process_name, datetime_str, forecast_minutes],
178
+ outputs=[result_df, summary_output, json_output]
179
+ )
180
+
181
+ if __name__ == "__main__":
182
+ use_mcp = os.getenv("USE_MCP", "0") == "1"
183
+ if use_mcp:
184
+ demo.launch(mcp_server=True)
185
+ else:
186
+ demo.launch(server_name="0.0.0.0", share=False)