diff --git a/README.md b/README.md new file mode 100644 index 0000000..59e4c19 --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# Safe Launch 報告轉換系統 + +這是一個基於Flask的網頁應用,用於處理SPC的Raw data (.xls/.xlsx),自動化執行數據轉換與統計分析。 + +## 功能特色 + +- 支援 .xls 和 .xlsx 檔案格式 +- 自動化數據分組與聚合 +- 計算統計數據(最小值、最大值、平均值、標準差、PPK) +- 四捨五入至小數點第三位 +- 美觀的現代化網頁界面 +- 即時處理進度顯示 + +## 安裝與部署 + +### 1. 安裝依賴 +```bash +pip install -r requirements.txt +``` + +### 2. 啟動應用 +```bash +python app.py +``` + +### 3. 訪問應用 +打開瀏覽器訪問:`http://localhost:12001` + +## 項目結構 + +``` +data_transform/ +├── app.py # Flask主應用 +├── transform_data.py # 數據處理核心邏輯 +├── requirements.txt # Python依賴包 +├── templates/ +│ └── index.html # 網頁模板 +└── uploads/ # 上傳文件存儲目錄 +``` + +## 使用說明 + +1. 打開網頁應用 +2. 點擊「選擇檔案」按鈕 +3. 選擇要處理的Excel檔案(.xls或.xlsx格式) +4. 點擊「上傳並處理」按鈕 +5. 等待處理完成後自動下載結果檔案 + +## 技術架構 + +- **後端**: Flask (Python) +- **前端**: HTML5 + Bootstrap 4 + jQuery +- **數據處理**: pandas + numpy +- **文件處理**: openpyxl + xlrd + +## 注意事項 + +- 確保Excel檔案包含名為"Sheet1"的工作表 +- 處理大檔案時可能需要較長時間 +- 建議在生產環境中使用WSGI服務器(如Gunicorn) \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..6f57547 --- /dev/null +++ b/app.py @@ -0,0 +1,69 @@ + + +import os +from flask import Flask, request, render_template, send_from_directory, flash, redirect, url_for +from werkzeug.utils import secure_filename +from transform_data import process_with_rounding + +# --- 設定 --- +UPLOAD_FOLDER = 'uploads' # 用於儲存上傳和處理後的檔案 +ALLOWED_EXTENSIONS = {'xls', 'xlsx'} + +app = Flask(__name__) +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER +app.config['SECRET_KEY'] = 'supersecretkey' # Flask 需要一個密鑰來顯示提示訊息 + +# --- 輔助函式 --- +def allowed_file(filename): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + +# --- 路由定義 --- +@app.route('/', methods=['GET', 'POST']) +def upload_file(): + if request.method == 'POST': + # 檢查是否有上傳檔案 + if 'file' not in request.files: + flash('沒有檔案部分') + return redirect(request.url) + file = request.files['file'] + # 如果使用者未選擇檔案,瀏覽器也會送出一個沒有檔名的空檔案 + if file.filename == '': + flash('未選擇檔案') + return redirect(request.url) + + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + + # 建立上傳資料夾 (如果不存在) + if not os.path.exists(app.config['UPLOAD_FOLDER']): + os.makedirs(app.config['UPLOAD_FOLDER']) + + input_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + file.save(input_path) + + # 產生最終檔案名稱 + base, ext = os.path.splitext(filename) + final_filename = f"{base}_final_rounded.xlsx" + + # 呼叫您既有的處理函式 + try: + process_with_rounding(input_path, final_filename) + # 提供下載連結 + return redirect(url_for('download_file', name=final_filename)) + except Exception as e: + flash(f'處理檔案時發生錯誤: {e}') + return redirect(request.url) + + return render_template('index.html') + +@app.route('/uploads/') +def download_file(name): + return send_from_directory(app.config['UPLOAD_FOLDER'], name) + +# --- 啟動伺服器 --- +if __name__ == '__main__': + # 使用 host='0.0.0.0' 讓區域網路中的其他電腦可以存取 + # 在生產環境中,建議設置 debug=False + app.run(debug=False, host='0.0.0.0', port=12001) + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7eab40c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +Flask==2.3.3 +pandas==2.0.3 +numpy==1.24.3 +openpyxl==3.1.2 +xlrd==2.0.1 +Werkzeug==2.3.7 \ No newline at end of file diff --git a/transform_data.py b/transform_data.py new file mode 100644 index 0000000..0a7703d --- /dev/null +++ b/transform_data.py @@ -0,0 +1,132 @@ + +import pandas as pd +import os +import numpy as np + +def process_with_rounding(input_path, final_filename): + """ + 讀取Excel,執行分組聚合,轉置,計算統計數據(包含PPK)並四捨五入至小數點第三位,最後儲存。 + """ + try: + print(f"正在讀取檔案: {input_path}") + df = pd.read_excel(input_path, sheet_name='Sheet1', engine='xlrd') + print("檔案讀取成功。") + + # --- 1. 根據位置定義欄位 --- + id_cols_indices = [2, 6, 8, 9, 10] # C, G, I, J, K + id_vars_names = df.columns[id_cols_indices].tolist() + value_col_start_index = 19 # T欄位 + value_vars = df.columns[value_col_start_index:].tolist() + + special_cat_col_name = df.columns[6] + lsl_col_name = df.columns[9] + usl_col_name = df.columns[10] + + # --- 2. 修正分組鍵,確保空值準確性 --- + def to_grouping_str(x): + if not pd.notna(x): return '' + if isinstance(x, (int, float)) and x == int(x): return str(int(x)) + return str(x) + + df[special_cat_col_name] = df[special_cat_col_name].apply(to_grouping_str) + df[lsl_col_name] = df[lsl_col_name].apply(to_grouping_str) + df[usl_col_name] = df[usl_col_name].apply(to_grouping_str) + print("已將分組鍵轉換為文字以進行精確分組。") + + # --- 3. 執行分組與聚合 --- + def flatten_group_values(series): + return [item for item in series.values.flatten() if pd.notna(item)] + + print("開始進行分組與數據合併...") + grouped = df.groupby(id_vars_names, dropna=False)[value_vars] + aggregated_series = grouped.apply(flatten_group_values) + + if aggregated_series.empty or all(len(v) == 0 for v in aggregated_series): + print("警告:分組後未發現任何可合併的數據。") + return + + # --- 4. 計算統計數據與PPK,並進行四捨五入 --- + print("正在為每個分組計算統計數據與PPK...") + final_df = aggregated_series.reset_index(name='Aggregated_Values') + + def calculate_and_round_stats(row): + values = pd.Series(row['Aggregated_Values']) + lsl_str = row[lsl_col_name] + usl_str = row[usl_col_name] + + if values.empty: + return pd.Series([np.nan, np.nan, np.nan, np.nan, np.nan]) + + mean = values.mean() + std = values.std() + min_val = values.min() + max_val = values.max() + ppk = np.nan + + if std is not None and std > 0: + try: + usl = float(usl_str) + has_usl = True + except (ValueError, TypeError): has_usl = False + try: + lsl = float(lsl_str) + has_lsl = True + except (ValueError, TypeError): has_lsl = False + + if has_usl and has_lsl: + ppu = (usl - mean) / (3 * std) + ppl = (mean - lsl) / (3 * std) + ppk = min(ppu, ppl) + elif has_usl: + ppk = (usl - mean) / (3 * std) + elif has_lsl: + ppk = (mean - lsl) / (3 * std) + + # *** 修改:對所有結果進行四捨五入到小數點後三位 *** + return pd.Series([ + round(min_val, 3) if pd.notna(min_val) else min_val, + round(max_val, 3) if pd.notna(max_val) else max_val, + round(mean, 3) if pd.notna(mean) else mean, + round(std, 3) if pd.notna(std) else std, + round(ppk, 3) if pd.notna(ppk) else ppk + ]) + + stats_df = final_df.apply(calculate_and_round_stats, axis=1) + stats_df.columns = ['最小值', '最大值', '平均值', '標準差', 'PPK'] + + stats_with_ids = pd.concat([final_df[id_vars_names], stats_df], axis=1) + stats_transposed = stats_with_ids.set_index(id_vars_names).T + print("統計數據計算與格式化完成。") + + # --- 5. 準備並轉置主要數據 --- + print("正在準備與轉置主要數據...") + expanded_values = final_df['Aggregated_Values'].apply(pd.Series) + expanded_values.columns = [i + 1 for i in range(expanded_values.shape[1])] + result_df = pd.concat([final_df[id_vars_names], expanded_values], axis=1) + transposed_df = result_df.set_index(id_vars_names).T + transposed_df.index.name = "量測值編號" + print("主要數據轉置完成。") + + # --- 6. 合併主要數據與統計數據 --- + final_result_df = pd.concat([transposed_df, stats_transposed]) + print("已將統計結果附加到數據末尾。") + + # --- 7. 儲存最終檔案 --- + output_dir = os.path.dirname(input_path) + final_output_path = os.path.join(output_dir, final_filename) + final_result_df.to_excel(final_output_path, index=True, engine='openpyxl') + print(f"成功!格式化後的最終檔案已儲存至: {final_output_path}") + + except FileNotFoundError: + print(f"錯誤:找不到檔案 {input_path}") + except Exception as e: + print(f"處理過程中發生未預期的錯誤: {e}") + +if __name__ == "__main__": + # 這個區塊現在僅供直接執行此腳本時測試用 + # 當作為模組被 app.py 匯入時,此區塊不會被執行 + print("此腳本現在是作為一個模組,請透過 app.py 啟動網頁服務來使用。") + # 以下是測試範例,您可以取消註解來進行單獨測試: + # input_file_path = r'GA25072023.xls' # 假設測試檔案在同個資料夾 + # final_file_name = 'GA25072023_final_rounded_test.xlsx' + # process_with_rounding(input_file_path, final_file_name) diff --git a/啟動網頁版的步驟.txt b/啟動網頁版的步驟.txt new file mode 100644 index 0000000..bf1d5d7 --- /dev/null +++ b/啟動網頁版的步驟.txt @@ -0,0 +1,31 @@ +# Safe Launch 報告轉換系統 - 啟動步驟 + +## 方法一:直接啟動 +1. 打開您電腦的「命令提示字元 (cmd)」或「Windows PowerShell」 +2. 切換到項目目錄: + cd "您的項目路徑\data_transform" +3. 安裝依賴包(首次使用): + pip install -r requirements.txt +4. 啟動網頁伺服器: + python app.py +5. 打開瀏覽器訪問:http://localhost:12001 + +## 方法二:使用虛擬環境(推薦) +1. 創建虛擬環境: + python -m venv venv +2. 啟動虛擬環境: + venv\Scripts\activate +3. 安裝依賴包: + pip install -r requirements.txt +4. 啟動應用: + python app.py +5. 訪問:http://localhost:12001 + +## 網路訪問 +如果要在區域網路中讓其他電腦訪問,應用會自動綁定到 0.0.0.0:12001 +您可以使用本機IP地址訪問,例如:http://192.168.1.100:12001 + +## 注意事項 +- 確保防火牆允許12001端口 +- 生產環境建議使用WSGI服務器(如Gunicorn) +- 處理大檔案時請耐心等待 \ No newline at end of file