Ken-INOUE commited on
Commit
71ff920
·
1 Parent(s): 3875611

Enhance diagnosis function with improved error handling and detailed result output; update Gradio UI for better user interaction and clarity

Browse files
Files changed (1) hide show
  1. app.py +122 -95
app.py CHANGED
@@ -1,8 +1,11 @@
 
 
1
  import gradio as gr
2
  import pandas as pd
 
3
  import json
4
 
5
- # --- 状態判定関数 ---
6
  def judge_status(value, ll, l, h, hh):
7
  if pd.notna(ll) and value < ll:
8
  return "LOW-LOW"
@@ -15,104 +18,132 @@ def judge_status(value, ll, l, h, hh):
15
  else:
16
  return "OK"
17
 
18
- # --- 閾値診断処理 ---
 
 
 
 
 
 
19
  def diagnose_process_range(csv_file, excel_file, process_name, datetime_str, window_minutes):
20
  try:
21
- # CSV読み込み
22
  df = pd.read_csv(csv_file.name, header=[0, 1, 2])
23
  timestamp_col = df.iloc[:, 0]
24
  df = df.drop(df.columns[0], axis=1)
25
  df.insert(0, "timestamp", timestamp_col)
26
  df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
27
 
28
- # 閾値テーブル読み込み
29
  thresholds_df = pd.read_excel(excel_file.name)
30
  thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE": True, "FALSE": False})
31
  for col in ["LL", "L", "H", "HH"]:
32
  if col in thresholds_df.columns:
33
  thresholds_df[col] = pd.to_numeric(thresholds_df[col], errors="coerce")
34
 
35
- # --- 入力日時処理 ---
 
 
 
 
36
  target_time = pd.to_datetime(datetime_str)
37
- start_time = target_time - pd.Timedelta(minutes=window_minutes)
38
- end_time = target_time
39
- df_window = df[(df["timestamp"] >= start_time) & (df["timestamp"] <= end_time)]
40
-
41
- if df_window.empty:
42
- return None, " 指定時間幅にデータなし", None
43
-
44
- proc_thresholds = thresholds_df[thresholds_df["ProcessNo_ProcessName"] == process_name]
45
- if proc_thresholds.empty:
46
- return None, f" プロセス {process_name} の閾値がありません", None
47
-
48
- # --- 処理 ---
49
- all_results = []
50
- for _, row in df_window.iterrows():
51
- for _, thr in proc_thresholds.iterrows():
52
- col_tuple = (thr["ColumnID"], thr["ItemName"], thr["ProcessNo_ProcessName"])
53
- if col_tuple not in df.columns:
54
- continue
55
- value = row[col_tuple]
56
- status = judge_status(value, thr.get("LL"), thr.get("L"), thr.get("H"), thr.get("HH"))
57
- all_results.append({
58
- "ItemName": thr["ItemName"],
59
- "判定": status,
60
- "重要項目": bool(thr.get("Important", False))
61
- })
62
-
63
- if not all_results:
64
- return None, f"⚠ データ不足", None
65
-
66
- # --- 集計(全項目) ---
67
- status_counts = pd.Series([r["判定"] for r in all_results]).value_counts().reindex(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
69
  )
70
- status_ratio = (status_counts / len(all_results) * 100).round(1)
71
- result_df_all = pd.DataFrame({"状態": status_counts.index, "件数": status_counts.values, "割合(%)": status_ratio.values})
72
-
73
- # --- 集計(重要項目全体) ---
74
- important_results = [r for r in all_results if r["重要項目"]]
75
- if important_results:
76
- status_counts_imp = pd.Series([r["判定"] for r in important_results]).value_counts().reindex(
77
- ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
78
- )
79
- status_ratio_imp = (status_counts_imp / len(important_results) * 100).round(1)
80
- result_df_imp = pd.DataFrame({"状態": status_counts_imp.index, "件数": status_counts_imp.values, "割合(%)": status_ratio_imp.values})
81
- else:
82
- result_df_imp = pd.DataFrame(columns=["状態", "件数", "割合(%)"])
83
-
84
- # --- 集計(重要項目ごと) ---
85
- result_per_item = []
86
- for item in set([r["ItemName"] for r in important_results]):
87
- item_results = [r for r in important_results if r["ItemName"] == item]
88
- if not item_results:
89
- continue
90
- status_counts_item = pd.Series([r["判定"] for r in item_results]).value_counts().reindex(
91
- ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
92
- )
93
- status_ratio_item = (status_counts_item / len(item_results) * 100).round(1)
94
- for s, c, r in zip(status_counts_item.index, status_counts_item.values, status_ratio_item.values):
95
- result_per_item.append({"ItemName": item, "状態": s, "件数": int(c), "割合(%)": float(r)})
96
- result_df_imp_items = pd.DataFrame(result_per_item)
97
-
98
- # --- JSON出力(集計のみ) ---
99
- summary_stats = {
100
- "全項目割合": {k: float(v) for k, v in status_ratio.to_dict().items()},
101
- "重要項目全体割合": {k: float(v) for k, v in status_ratio_imp.to_dict().items()} if not result_df_imp.empty else {},
102
- "重要項目ごと割合": result_per_item
103
- }
104
- result_json = json.dumps({"集計結果": summary_stats}, ensure_ascii=False, indent=2)
105
-
106
- # --- サマリー ---
107
- summary = (
108
- f"✅ {process_name} の診断完了({start_time} ~ {end_time})\n"
109
- + "[全項目] " + " / ".join([f"{s}:{r:.1f}%" for s, r in status_ratio.items()])
110
  )
 
111
 
112
- return result_df_all, result_df_imp, result_df_imp_items, summary, result_json
 
 
 
 
 
 
 
 
 
 
113
 
114
- except Exception as e:
115
- return None, None, None, f"❌ エラー: {str(e)}", None
116
 
117
 
118
  # --- Gradio UI ---
@@ -120,29 +151,25 @@ with gr.Blocks() as demo:
120
  gr.Markdown("## 閾値診断アプリ")
121
 
122
  with gr.Row():
123
- csv_input = gr.File(label="CSVファイルをアップロード", type="filepath")
124
- excel_input = gr.File(label="閾値テーブExcelをアップロード", type="filepath")
125
 
126
  process_name = gr.Textbox(label="プロセス名", value="E018-A012_除害RO")
127
- datetime_str = gr.Textbox(label="日時 (例: 2025/8/1 1:05)")
128
- window_minutes = gr.Number(label="さかのぼる時間幅 ()", value=60)
129
-
130
- run_button = gr.Button("診断を実行")
131
 
132
- with gr.Tab("全項目集計"):
133
- result_all = gr.Dataframe(label="全項目の状態集計結果")
134
- with gr.Tab("重要項目全体集計"):
135
- result_imp = gr.Dataframe(label="重要項目全体の状態集計結果")
136
- with gr.Tab("重要項目ごと集計"):
137
- result_imp_items = gr.Dataframe(label="重要項目ごとの状態集計結果")
138
 
 
 
 
139
  summary_output = gr.Textbox(label="サマリー")
140
- json_output = gr.JSON(label="JSON出力")
141
 
142
- run_button.click(
143
- fn=diagnose_process_range,
144
  inputs=[csv_input, excel_input, process_name, datetime_str, window_minutes],
145
- outputs=[result_all, result_imp, result_imp_items, summary_output, json_output]
146
  )
147
 
148
  if __name__ == "__main__":
 
1
+ # 閾値診断アプリ Gradio版
2
+
3
  import gradio as gr
4
  import pandas as pd
5
+ import numpy as np
6
  import json
7
 
8
+ # --- ユーティリティ ---
9
  def judge_status(value, ll, l, h, hh):
10
  if pd.notna(ll) and value < ll:
11
  return "LOW-LOW"
 
18
  else:
19
  return "OK"
20
 
21
+ def convert_value(v):
22
+ """numpy型をPython標準型に変換"""
23
+ if hasattr(v, "item"):
24
+ return v.item()
25
+ return float(v) if isinstance(v, (np.floating, float)) else int(v) if isinstance(v, (np.integer, int)) else v
26
+
27
+ # --- 診断関数 ---
28
  def diagnose_process_range(csv_file, excel_file, process_name, datetime_str, window_minutes):
29
  try:
30
+ # CSV読み込み(3行ヘッダー)
31
  df = pd.read_csv(csv_file.name, header=[0, 1, 2])
32
  timestamp_col = df.iloc[:, 0]
33
  df = df.drop(df.columns[0], axis=1)
34
  df.insert(0, "timestamp", timestamp_col)
35
  df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
36
 
37
+ # 閾値テーブル
38
  thresholds_df = pd.read_excel(excel_file.name)
39
  thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE": True, "FALSE": False})
40
  for col in ["LL", "L", "H", "HH"]:
41
  if col in thresholds_df.columns:
42
  thresholds_df[col] = pd.to_numeric(thresholds_df[col], errors="coerce")
43
 
44
+ except Exception as e:
45
+ return None, None, None, f"❌ 入力ファイルの読み込みに失敗しました: {e}", None
46
+
47
+ # 対象期間抽出
48
+ try:
49
  target_time = pd.to_datetime(datetime_str)
50
+ except Exception:
51
+ return None, None, None, f"⚠ 入力した日時 {datetime_str} が無効です。", None
52
+
53
+ start_time = target_time - pd.Timedelta(minutes=window_minutes)
54
+ end_time = target_time
55
+ df_window = df[(df["timestamp"] >= start_time) & (df["timestamp"] <= end_time)]
56
+ if df_window.empty:
57
+ return None, None, None, "⚠ 指定した時間幅にデータが見つかりません。", None
58
+
59
+ proc_thresholds = thresholds_df[thresholds_df["ProcessNo_ProcessName"] == process_name]
60
+ if proc_thresholds.empty:
61
+ return None, None, None, f"⚠ プロセス {process_name} の閾値が設されていません。", None
62
+
63
+ # --- 判定結果 ---
64
+ all_results = []
65
+ for _, row in df_window.iterrows():
66
+ for _, thr in proc_thresholds.iterrows():
67
+ col_tuple = (thr["ColumnID"], thr["ItemName"], thr["ProcessNo_ProcessName"])
68
+ if col_tuple not in df.columns:
69
+ continue
70
+ value = row[col_tuple]
71
+ status = judge_status(value, thr.get("LL"), thr.get("L"), thr.get("H"), thr.get("HH"))
72
+ all_results.append({
73
+ "ColumnID": thr["ColumnID"],
74
+ "ItemName": thr["ItemName"],
75
+ "判定": status,
76
+ "重要項目": bool(thr.get("Important", False)),
77
+ "時刻": str(row["timestamp"])
78
+ })
79
+
80
+ # --- 集計(全項目) ---
81
+ total = len(all_results)
82
+ status_counts = pd.Series([r["判定"] for r in all_results]).value_counts().reindex(
83
+ ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
84
+ )
85
+ status_ratio = (status_counts / total * 100).round(1)
86
+ result_df_all = pd.DataFrame({
87
+ "状態": status_counts.index,
88
+ "件数": status_counts.values,
89
+ "割合(%)": status_ratio.values
90
+ })
91
+
92
+ # --- 集計(重要項目全体) ---
93
+ important_results = [r for r in all_results if r["重要項目"]]
94
+ if important_results:
95
+ total_imp = len(important_results)
96
+ status_counts_imp = pd.Series([r["判定"] for r in important_results]).value_counts().reindex(
97
  ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
98
  )
99
+ status_ratio_imp = (status_counts_imp / total_imp * 100).round(1)
100
+ result_df_imp = pd.DataFrame({
101
+ "状態": status_counts_imp.index,
102
+ "件数": status_counts_imp.values,
103
+ "割合(%)": status_ratio_imp.values
104
+ })
105
+ else:
106
+ result_df_imp = pd.DataFrame(columns=["状態", "件数", "割合(%)"])
107
+ status_ratio_imp = pd.Series(dtype=float)
108
+
109
+ # --- 集計(重要項目ごと) ---
110
+ result_per_item = []
111
+ for item in [r["ItemName"] for r in important_results]:
112
+ item_results = [r for r in important_results if r["ItemName"] == item]
113
+ if not item_results:
114
+ continue
115
+ total_item = len(item_results)
116
+ status_counts_item = pd.Series([r["判定"] for r in item_results]).value_counts().reindex(
117
+ ["LOW-LOW", "LOW", "OK", "HIGH", "HIGH-HIGH"], fill_value=0
118
+ )
119
+ status_ratio_item = (status_counts_item / total_item * 100).round(1)
120
+ for s, c, r in zip(status_counts_item.index, status_counts_item.values, status_ratio_item.values):
121
+ result_per_item.append({"ItemName": item, "状態": s, "件数": c, "割合(%)": r})
122
+ result_df_imp_items = pd.DataFrame(result_per_item)
123
+
124
+ # --- サマリー ---
125
+ summary = (
126
+ f"✅ {process_name} の診断完了({start_time} ~ {end_time})\n"
127
+ + "[全項目] " + " / ".join([f"{s}:{r:.1f}%" for s, r in status_ratio.items()]) + "\n"
128
+ + "[重要項目全体] " + (
129
+ " / ".join([f"{s}:{r:.1f}%" for s, r in status_ratio_imp.items()])
130
+ if not result_df_imp.empty else "対象データなし"
 
 
 
 
 
 
 
 
131
  )
132
+ )
133
 
134
+ # --- JSON(集計結果のみ、UIと一致) ---
135
+ json_data = {
136
+ "集計結果": {
137
+ "全項目割合": {k: convert_value(v) for k, v in status_ratio.to_dict().items()},
138
+ "重要項目全体割合": {k: convert_value(v) for k, v in status_ratio_imp.to_dict().items()} if not result_df_imp.empty else {},
139
+ "重要項目ごと割合": [
140
+ {k: convert_value(v) for k, v in row.items()} for _, row in result_df_imp_items.iterrows()
141
+ ]
142
+ }
143
+ }
144
+ result_json = json.dumps(json_data, ensure_ascii=False, indent=2)
145
 
146
+ return result_df_all, result_df_imp, result_df_imp_items, summary, result_json
 
147
 
148
 
149
  # --- Gradio UI ---
 
151
  gr.Markdown("## 閾値診断アプリ")
152
 
153
  with gr.Row():
154
+ csv_input = gr.File(label="CSVファイルをアップロード", file_types=[".csv"], type="filepath")
155
+ excel_input = gr.File(label="Excel閾値ファイルをアップロード", file_types=[".xlsx"], type="filepath")
156
 
157
  process_name = gr.Textbox(label="プロセス名", value="E018-A012_除害RO")
158
+ datetime_str = gr.Textbox(label="診断基準日時 (例: 2025/8/1 1:05)", value="2025/8/1 1:05")
159
+ window_minutes = gr.Number(label="さかのぼる時間幅", value=60)
 
 
160
 
161
+ run_btn = gr.Button("診断を実行")
 
 
 
 
 
162
 
163
+ result_df_all = gr.Dataframe(label="全項目の状態集計結果")
164
+ result_df_imp = gr.Dataframe(label="重要項目全体の状態集計結果")
165
+ result_df_imp_items = gr.Dataframe(label="重要項目ごとの状態集計結果")
166
  summary_output = gr.Textbox(label="サマリー")
167
+ json_output = gr.Json(label="JSON集計結果")
168
 
169
+ run_btn.click(
170
+ diagnose_process_range,
171
  inputs=[csv_input, excel_input, process_name, datetime_str, window_minutes],
172
+ outputs=[result_df_all, result_df_imp, result_df_imp_items, summary_output, json_output]
173
  )
174
 
175
  if __name__ == "__main__":