diff --git a/app/model/suitability.py b/app/model/suitability.py new file mode 100644 index 0000000..91ba74d --- /dev/null +++ b/app/model/suitability.py @@ -0,0 +1,55 @@ +"""适当性判定 → risk_suitability_log 行映射(R-02 P0 契约)。""" + +from __future__ import annotations + +from typing import Any, Literal + +CheckSource = Literal["r02_trade", "r02_chat", "c11_inquiry", "manual"] + + +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: list[str] = [] + match_result = check.get("match_result") or "forbidden" + 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 { + "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, + }