"""产品推荐路由(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/", 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("/", methods=["DELETE"]) @jwt_required def delete_proposal(proposal_id): """删除推荐方案。""" result = recommend_service.delete(proposal_id) return jsonify(result) @recommend_bp.route("//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("//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)