- 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
156 lines
4.7 KiB
Python
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
|