Refactor trend detection application to enhance CSV and Excel file handling, improve future forecasting logic, and update JSON output to use temporary files. Adjust Gradio UI for better user experience and output display.
Browse files
app.py
CHANGED
|
@@ -1,24 +1,29 @@
|
|
| 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
|
|
|
|
| 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(
|
| 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(
|
| 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:
|
|
@@ -35,7 +40,7 @@ def detect_trends_with_forecast(process_name, datetime_str, window_minutes, fore
|
|
| 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 |
|
|
@@ -64,8 +69,8 @@ def detect_trends_with_forecast(process_name, datetime_str, window_minutes, fore
|
|
| 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)
|
|
@@ -95,22 +100,33 @@ def detect_trends_with_forecast(process_name, datetime_str, window_minutes, fore
|
|
| 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":
|
|
|
|
|
|
|
|
|
|
| 105 |
})
|
| 106 |
|
| 107 |
result_df = pd.DataFrame(results)
|
|
|
|
|
|
|
| 108 |
result_json = json.dumps(results, ensure_ascii=False, indent=2)
|
| 109 |
|
| 110 |
-
#
|
| 111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
-
|
|
|
|
| 114 |
|
| 115 |
except Exception as e:
|
| 116 |
return None, f"❌ エラー: {str(e)}", None
|
|
@@ -134,8 +150,9 @@ with gr.Blocks() as demo:
|
|
| 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(
|
|
@@ -145,7 +162,6 @@ with gr.Blocks() as demo:
|
|
| 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)
|
|
|
|
| 1 |
+
# 傾向検出アプリ(Gradio版・重要項目ごとの詳細+JSONダウンロード)修正版
|
| 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 tempfile
|
| 9 |
+
import os
|
| 10 |
|
| 11 |
# --- 状態判定&未来予測関数 ---
|
| 12 |
def detect_trends_with_forecast(process_name, datetime_str, window_minutes, forecast_minutes, csv_file, excel_file):
|
| 13 |
try:
|
| 14 |
+
# ★ 修正ポイント: gr.File(type="filepath") は文字列パスが来る
|
| 15 |
+
csv_path = csv_file
|
| 16 |
+
excel_path = excel_file
|
| 17 |
+
|
| 18 |
# CSV読み込み(3行ヘッダー)
|
| 19 |
+
df = pd.read_csv(csv_path, header=[0, 1, 2])
|
| 20 |
timestamp_col = df.iloc[:, 0]
|
| 21 |
df = df.drop(df.columns[0], axis=1)
|
| 22 |
df.insert(0, "timestamp", timestamp_col)
|
| 23 |
df["timestamp"] = pd.to_datetime(df["timestamp"], errors="coerce")
|
| 24 |
|
| 25 |
# 閾値テーブル読み込み
|
| 26 |
+
thresholds_df = pd.read_excel(excel_path)
|
| 27 |
thresholds_df["Important"] = thresholds_df["Important"].astype(str).str.upper().map({"TRUE": True, "FALSE": False})
|
| 28 |
for col in ["LL", "L", "H", "HH"]:
|
| 29 |
if col in thresholds_df.columns:
|
|
|
|
| 40 |
|
| 41 |
# サンプリング間隔
|
| 42 |
interval = df_window["timestamp"].diff().median()
|
| 43 |
+
if pd.isna(interval) or interval == pd.Timedelta(0):
|
| 44 |
return None, "⚠ サンプリング間隔を検出できません", None
|
| 45 |
interval_minutes = interval.total_seconds() / 60
|
| 46 |
|
|
|
|
| 69 |
last_val = series.iloc[-1]
|
| 70 |
n = len(series)
|
| 71 |
|
| 72 |
+
# 未来予測(最小1ステップ)
|
| 73 |
+
forecast_steps = max(1, int(round(forecast_minutes / interval_minutes)))
|
| 74 |
forecast_index = n + forecast_steps
|
| 75 |
forecast_val = model.predict([[forecast_index]])[0][0]
|
| 76 |
forecast_time = target_time + pd.Timedelta(minutes=forecast_minutes)
|
|
|
|
| 100 |
results.append({
|
| 101 |
"ItemName": thr["ItemName"],
|
| 102 |
"傾向": status,
|
| 103 |
+
"傾き": float(round(slope, 4)),
|
| 104 |
+
"最終値": float(round(float(last_val), 3)) if pd.notna(last_val) else None,
|
| 105 |
+
"予測値": float(round(float(forecast_val), 3)),
|
| 106 |
"予測時刻": str(forecast_time),
|
| 107 |
"予測傾向": forecast_status,
|
| 108 |
+
"サンプリング間隔(分)": float(interval_minutes),
|
| 109 |
+
"LL": None if pd.isna(ll) else float(ll),
|
| 110 |
+
"L": None if pd.isna(l) else float(l),
|
| 111 |
+
"H": None if pd.isna(h) else float(h),
|
| 112 |
+
"HH": None if pd.isna(hh) else float(hh)
|
| 113 |
})
|
| 114 |
|
| 115 |
result_df = pd.DataFrame(results)
|
| 116 |
+
|
| 117 |
+
# JSONテキスト
|
| 118 |
result_json = json.dumps(results, ensure_ascii=False, indent=2)
|
| 119 |
|
| 120 |
+
# ★ 修正ポイント: BytesIO ではなく、一時ファイルに保存して「パス」を返す
|
| 121 |
+
fd, tmp_path = tempfile.mkstemp(suffix=".json", prefix="trend_result_")
|
| 122 |
+
os.close(fd)
|
| 123 |
+
with open(tmp_path, "w", encoding="utf-8") as f:
|
| 124 |
+
f.write(result_json)
|
| 125 |
+
|
| 126 |
+
summary = f"✅ {process_name} の傾向検出+未来予測完了({start_time}~{end_time}, 予測+{forecast_minutes}分)"
|
| 127 |
|
| 128 |
+
# gr.File 出力には「ファイルパス(文字列)」を返す
|
| 129 |
+
return result_df, summary, tmp_path
|
| 130 |
|
| 131 |
except Exception as e:
|
| 132 |
return None, f"❌ エラー: {str(e)}", None
|
|
|
|
| 150 |
run_btn = gr.Button("実行")
|
| 151 |
|
| 152 |
with gr.Row():
|
| 153 |
+
result_table = gr.Dataframe(label="傾向+未来予測結果", wrap=True)
|
| 154 |
summary_output = gr.Textbox(label="サマリー")
|
| 155 |
+
# ★ 修正ポイント: gr.File の出力にはパスを渡す。type指定は不要(出力)
|
| 156 |
json_download = gr.File(label="JSON結果ダウンロード")
|
| 157 |
|
| 158 |
run_btn.click(
|
|
|
|
| 162 |
)
|
| 163 |
|
| 164 |
if __name__ == "__main__":
|
|
|
|
| 165 |
use_mcp = os.getenv("USE_MCP", "0") == "1"
|
| 166 |
if use_mcp:
|
| 167 |
demo.launch(server_name="0.0.0.0", mcp_server=True)
|