Files
group_xinghuo_jinrong/app/model/suitability.py
T
zhanghongyu_0626 ef56c56435 feat(api): 实现代销平台 API v0.1,包括客户、产品、理财师、合规及员工接口
- 新增多个 API 路由:`/api/customers`, `/api/products`, `/api/advisors`, `/api/compliance`, `/api/staff`,支持客户信息、产品详情、理财师客户列表、合规判定及员工上下文查询。
- 引入平台服务层,封装核心只读操作,支持数据脱敏功能。
- 更新依赖注入,确保各 API 路由的权限控制与数据访问一致性。
- 添加相应的单元测试,确保新接口的功能完整性与稳定性。

此更新为代销平台提供了基础的 REST API 支持,增强了系统的可扩展性与可维护性。
2026-09-08 21:01:07 +08:00

66 lines
2.6 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.
"""适当性判定 → risk_suitability_log 行映射(R-02 P0 契约)。"""
from __future__ import annotations
from typing import Any, Literal
CheckSource = Literal["r02_trade", "r02_chat", "c11_inquiry", "manual", "platform"]
def compute_rule_refs(check: dict[str, Any]) -> list[str]:
"""由 check 结果计算 rule_refs(JR-AST/FM 规则编号体系)。
独立公共函数(AL-05 抽取,main 逻辑不变):供 build_suitability_log_row
落库与 service 层 SuitabilityResult.rule_refs 共用,避免两处漂移。
"""
rule_refs: list[str] = []
mismatch_type = check.get("mismatch_type") or "not_found"
if mismatch_type == "risk_level":
rule_refs.append("JR-AST-012")
if check.get("risk_is_expired") or mismatch_type == "risk_expired":
rule_refs.append("FM-03")
if check.get("needs_branch_confirm") or mismatch_type == "age_branch_confirm":
rule_refs.append("FM-01")
if mismatch_type == "professional_exempt":
rule_refs.append("JR-AST-PRO")
return rule_refs
def build_suitability_log_row(
check: dict[str, Any],
*,
trace_id: str,
actor_id: str,
check_source: CheckSource = "r02_trade",
request_ref: str | None = None,
profile_l1_version: int | None = None,
) -> dict[str, Any]:
"""将 CoreReadOnlyRepository.check_suitability 结果转为 INSERT 用 dict。"""
rule_refs = compute_rule_refs(check)
match_result = check.get("match_result") or "forbidden"
mismatch_type = check.get("mismatch_type") or "not_found"
return {
"trace_id": trace_id,
"customer_id": check.get("customer_id") or "",
"product_id": check.get("product_id") or "",
"product_name": check.get("product_name"),
"customer_risk_level": check.get("customer_risk_code") or "C1",
"product_risk_level": check.get("product_risk_code") or "R1",
"investor_category": check.get("investor_category") or "ordinary",
"match_result": match_result,
"mismatch_type": mismatch_type,
"is_matched": 1 if check.get("matched") else 0,
"is_blocked": 1 if check.get("blocked") else 0,
"requires_disclosure": 1 if check.get("requires_disclosure") else 0,
"needs_branch_confirm": 1 if check.get("needs_branch_confirm") else 0,
"risk_was_expired": 1 if check.get("risk_is_expired") else 0,
"block_reason": check.get("reason"),
"block_response_code": check.get("block_response_code"),
"check_source": check_source,
"actor_id": actor_id,
"request_ref": request_ref,
"profile_l1_version": profile_l1_version,
"rule_refs": rule_refs or None,
}