"""适当性判定 → 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 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, }