Files
DIT_C/routes/api.py
DonaldFang 方士碩 44cd2f8e76 feat: DITAnalyzer module - Feature 6.2 & 6.3 implementation
- DITAnalyzer class with data preprocessing
- Feature 6.2: High value resource allocation analysis
- Feature 6.3: Stagnant deal alerts
- Flask API routes for CSV upload and analysis
- Test suite with sample data
2025-12-12 13:12:31 +08:00

156 lines
4.7 KiB
Python

"""
DIT 分析 API 路由
"""
import os
import json
from flask import Blueprint, request, jsonify, current_app
from werkzeug.utils import secure_filename
from services.dit_analyzer import DITAnalyzer, DITAnalyzerError
api_bp = Blueprint('api', __name__, url_prefix='/api')
ALLOWED_EXTENSIONS = {'csv'}
UPLOAD_FOLDER = 'uploads'
def allowed_file(filename: str) -> bool:
"""檢查檔案副檔名"""
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@api_bp.route('/analyze', methods=['POST'])
def analyze_dit():
"""
分析 DIT CSV 檔案
接受 multipart/form-data 上傳 CSV
回傳 JSON 格式分析結果
"""
# 檢查檔案
if 'file' not in request.files:
return jsonify({"error": "未上傳檔案", "code": "NO_FILE"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "未選擇檔案", "code": "NO_FILENAME"}), 400
if not allowed_file(file.filename):
return jsonify({"error": "僅支援 CSV 檔案", "code": "INVALID_TYPE"}), 400
# 取得參數
top_percent = float(request.form.get('top_percent', 0.2))
low_win_rate = float(request.form.get('low_win_rate', 0.1))
threshold_days = int(request.form.get('threshold_days', 60))
try:
# 確保上傳目錄存在
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
# 儲存檔案
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
# 執行分析
analyzer = DITAnalyzer(filepath)
report = analyzer.generate_report(
top_percent=top_percent,
low_win_rate=low_win_rate,
threshold_days=threshold_days
)
# 清理暫存檔
os.remove(filepath)
return jsonify({
"status": "success",
"data": report
})
except DITAnalyzerError as e:
return jsonify({"error": str(e), "code": "ANALYZER_ERROR"}), 400
except Exception as e:
return jsonify({"error": f"分析失敗: {str(e)}", "code": "INTERNAL_ERROR"}), 500
@api_bp.route('/analyze/resource-allocation', methods=['POST'])
def analyze_resource_allocation():
"""
僅執行 Feature 6.2: 高價值資源分配分析
"""
if 'file' not in request.files:
return jsonify({"error": "未上傳檔案", "code": "NO_FILE"}), 400
file = request.files['file']
if not allowed_file(file.filename):
return jsonify({"error": "僅支援 CSV 檔案", "code": "INVALID_TYPE"}), 400
top_percent = float(request.form.get('top_percent', 0.2))
low_win_rate = float(request.form.get('low_win_rate', 0.1))
try:
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
analyzer = DITAnalyzer(filepath)
results = analyzer.analyze_resource_allocation(top_percent, low_win_rate)
os.remove(filepath)
return jsonify({
"status": "success",
"data": {
"type": "resource_allocation",
"count": len(results),
"action_cards": results
}
})
except DITAnalyzerError as e:
return jsonify({"error": str(e), "code": "ANALYZER_ERROR"}), 400
except Exception as e:
return jsonify({"error": f"分析失敗: {str(e)}", "code": "INTERNAL_ERROR"}), 500
@api_bp.route('/analyze/stagnant-deals', methods=['POST'])
def analyze_stagnant_deals():
"""
僅執行 Feature 6.3: 呆滯案件警示
"""
if 'file' not in request.files:
return jsonify({"error": "未上傳檔案", "code": "NO_FILE"}), 400
file = request.files['file']
if not allowed_file(file.filename):
return jsonify({"error": "僅支援 CSV 檔案", "code": "INVALID_TYPE"}), 400
threshold_days = int(request.form.get('threshold_days', 60))
try:
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
filename = secure_filename(file.filename)
filepath = os.path.join(UPLOAD_FOLDER, filename)
file.save(filepath)
analyzer = DITAnalyzer(filepath)
results = analyzer.analyze_stagnant_deals(threshold_days)
os.remove(filepath)
return jsonify({
"status": "success",
"data": {
"type": "stagnant_deals",
"count": len(results),
"action_cards": results
}
})
except DITAnalyzerError as e:
return jsonify({"error": str(e), "code": "ANALYZER_ERROR"}), 400
except Exception as e:
return jsonify({"error": f"分析失敗: {str(e)}", "code": "INTERNAL_ERROR"}), 500