baodan/api/insurance/recommend/routes.py

172 lines
6.0 KiB
Python
Raw Normal View History

"""产品推荐路由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)