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
This commit is contained in:
155
routes/api.py
Normal file
155
routes/api.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user