172 lines
6.0 KiB
Python
172 lines
6.0 KiB
Python
"""产品推荐路由(A3)
|
||
|
||
POST /recommend/generate 生成推荐方案
|
||
GET /recommend/generate/{id} 查询生成状态
|
||
GET /recommend/list 推荐历史列表
|
||
POST /recommend/{id}/export 导出方案
|
||
POST /recommend/{id}/share 生成分享链接
|
||
"""
|
||
from flask import Blueprint, request, jsonify, send_file
|
||
from insurance.recommend.service import RecommendService
|
||
from insurance.middleware.auth_middleware import jwt_required
|
||
|
||
recommend_bp = Blueprint("recommend", __name__)
|
||
recommend_service = RecommendService()
|
||
|
||
|
||
@recommend_bp.route("/generate", methods=["POST"])
|
||
@jwt_required
|
||
def generate():
|
||
"""生成推荐方案(异步任务)。
|
||
|
||
参数校验按需求文档 §13.3:
|
||
- customer.age: 1-150 整数
|
||
- customer.gender: male/female
|
||
- customer.health_status: 健康/有既往病史/慢性病/重大疾病史
|
||
- customer.occupation: 1-64 字符
|
||
- customer.monthly_budget: >=1
|
||
- insurance_types: 1-10 项枚举
|
||
- coverage_amount: >=10000
|
||
- coverage_period: 枚举
|
||
"""
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({"code": 1001, "message": "请求体不能为空", "data": None}), 400
|
||
|
||
user_id = request.user_id
|
||
|
||
# 参数校验
|
||
errors = []
|
||
customer = data.get("customer", {})
|
||
if not customer:
|
||
errors.append("customer 不能为空")
|
||
|
||
# 年龄校验
|
||
age = customer.get("age")
|
||
if age is None:
|
||
errors.append("customer.age 不能为空")
|
||
elif not isinstance(age, int) or not (1 <= age <= 150):
|
||
errors.append("customer.age 必须是 1-150 的整数")
|
||
|
||
# 性别校验
|
||
gender = customer.get("gender", "")
|
||
if gender not in ("male", "female"):
|
||
errors.append("customer.gender 必须是 male 或 female")
|
||
|
||
# 健康状况校验
|
||
health_status = customer.get("health_status", "")
|
||
valid_health = ["健康", "有既往病史", "慢性病", "重大疾病史"]
|
||
if health_status and health_status not in valid_health:
|
||
errors.append(f"customer.health_status 必须是 {'/'.join(valid_health)} 之一")
|
||
|
||
# 职业校验
|
||
occupation = customer.get("occupation", "")
|
||
if not occupation or len(occupation) > 64:
|
||
errors.append("customer.occupation 不能为空且不超过64字符")
|
||
|
||
# 月预算校验
|
||
monthly_budget = customer.get("monthly_budget")
|
||
if monthly_budget is None or not isinstance(monthly_budget, (int, float)) or monthly_budget < 1:
|
||
errors.append("customer.monthly_budget 必须 >= 1")
|
||
|
||
# 险种校验
|
||
insurance_types = data.get("insurance_types", [])
|
||
valid_types = ["重疾险", "寿险", "医疗险", "意外险", "年金险", "储蓄险"]
|
||
if not insurance_types or not isinstance(insurance_types, list):
|
||
errors.append("insurance_types 不能为空")
|
||
elif len(insurance_types) > 10:
|
||
errors.append("insurance_types 最多选择10项")
|
||
else:
|
||
for t in insurance_types:
|
||
if t not in valid_types:
|
||
errors.append(f"无效的险种: {t}")
|
||
break
|
||
|
||
# 保额校验
|
||
coverage_amount = data.get("coverage_amount")
|
||
if coverage_amount is None or not isinstance(coverage_amount, (int, float)) or coverage_amount < 10000:
|
||
errors.append("coverage_amount 必须 >= 10000")
|
||
|
||
# 保障期限校验
|
||
coverage_period = data.get("coverage_period", "")
|
||
valid_periods = ["终身", "10年", "20年", "30年", "至60岁", "至65岁", "至70岁", "至80岁"]
|
||
if not coverage_period:
|
||
errors.append("coverage_period 不能为空")
|
||
elif coverage_period not in valid_periods:
|
||
errors.append(f"coverage_period 必须是 {'/'.join(valid_periods)} 之一")
|
||
|
||
if errors:
|
||
return jsonify({"code": 1001, "message": "; ".join(errors), "data": None}), 400
|
||
|
||
result = recommend_service.generate(user_id, data)
|
||
return jsonify(result)
|
||
|
||
|
||
@recommend_bp.route("/generate/<task_id>", methods=["GET"])
|
||
@jwt_required
|
||
def get_status(task_id):
|
||
"""查询生成任务状态。"""
|
||
result = recommend_service.get_status(task_id)
|
||
return jsonify(result)
|
||
|
||
|
||
@recommend_bp.route("/list", methods=["GET"])
|
||
@jwt_required
|
||
def list_proposals():
|
||
"""推荐历史列表(分页 + 筛选)。"""
|
||
params = {
|
||
"page": request.args.get("page", 1, type=int),
|
||
"page_size": request.args.get("page_size", 20, type=int),
|
||
"customer_name": request.args.get("customerName", ""),
|
||
"insurance_type": request.args.get("insuranceType", ""),
|
||
}
|
||
user_id = request.user_id
|
||
result = recommend_service.list_proposals(user_id, params)
|
||
return jsonify(result)
|
||
|
||
|
||
@recommend_bp.route("/<proposal_id>", methods=["DELETE"])
|
||
@jwt_required
|
||
def delete_proposal(proposal_id):
|
||
"""删除推荐方案。"""
|
||
result = recommend_service.delete(proposal_id)
|
||
return jsonify(result)
|
||
|
||
|
||
@recommend_bp.route("/<proposal_id>/export", methods=["POST"])
|
||
@jwt_required
|
||
def export_proposal(proposal_id):
|
||
"""导出方案为 PDF/Word。"""
|
||
data = request.get_json()
|
||
fmt = data.get("format", "pdf")
|
||
if fmt not in ("pdf", "docx"):
|
||
return jsonify({"code": 1001, "message": "不支持的导出格式,仅支持 pdf/docx", "data": None}), 400
|
||
|
||
from insurance.recommend.export_service import export_word, export_pdf
|
||
|
||
if fmt == "docx":
|
||
buf, error = export_word(proposal_id)
|
||
else:
|
||
buf, error = export_pdf(proposal_id)
|
||
|
||
if error:
|
||
return jsonify({"code": 1005, "message": error, "data": None}), 404
|
||
|
||
mimetype = "application/pdf" if fmt == "pdf" else "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||
return send_file(
|
||
buf,
|
||
mimetype=mimetype,
|
||
as_attachment=True,
|
||
download_name=f"recommendation_{proposal_id}.{fmt}",
|
||
)
|
||
|
||
|
||
@recommend_bp.route("/<proposal_id>/share", methods=["POST"])
|
||
@jwt_required
|
||
def share(proposal_id):
|
||
"""生成有时效的分享链接。"""
|
||
data = request.get_json()
|
||
expire_hours = data.get("expire_hours", 72)
|
||
result = recommend_service.share(proposal_id, expire_hours)
|
||
return jsonify(result)
|