上傳檔案到「/」
This commit is contained in:
60
README.md
Normal file
60
README.md
Normal file
@@ -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)
|
69
app.py
Normal file
69
app.py
Normal file
@@ -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/<name>')
|
||||||
|
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)
|
||||||
|
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
@@ -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
|
132
transform_data.py
Normal file
132
transform_data.py
Normal file
@@ -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)
|
31
啟動網頁版的步驟.txt
Normal file
31
啟動網頁版的步驟.txt
Normal file
@@ -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)
|
||||||
|
- 處理大檔案時請耐心等待
|
Reference in New Issue
Block a user