baodan/api/insurance/recommend/routes.py

172 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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