feat: 添加可配置场景注册表与规则引擎
- 新增场景基类、配置加载、注册表与原语模块 - 添加 r10_refund_split 规则及场景 JSON Schema - 扩展 scan 引擎与 scenarios API - 新增场景注册表/配置/集成测试 - 更新前端 App、api、labels 支持新场景
This commit is contained in:
@@ -10,6 +10,9 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from app.scenarios.base import BaseScenario, ClueDraft, ScanOutcome
|
||||
from app.scenarios.registry import register_scenario
|
||||
|
||||
|
||||
@dataclass
|
||||
class ContractRecord:
|
||||
@@ -76,3 +79,58 @@ def build_rationale(finding: SplitFinding, threshold: float, shared_controller:
|
||||
else:
|
||||
parts.append("建议进一步穿透客户关联关系以确认是否同一实控人。")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
@register_scenario
|
||||
class SplitContractScenario(BaseScenario):
|
||||
"""场景一拆单检测的插件封装:检测→评分→产出线索草稿。
|
||||
|
||||
数据通过构造注入(兼容现有 seed/测试的数据流);后续可改为从 session 自取数,
|
||||
接口保持不变。
|
||||
"""
|
||||
|
||||
code = "R8"
|
||||
title = "疑似政企拆单规避审批"
|
||||
risk_domain = "收入"
|
||||
label = "政企拆单"
|
||||
score_threshold = 0.0 # 命中即出线索(与原 run_split_contract_scan 的 score>0 一致)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
contracts: list[ContractRecord],
|
||||
approval_threshold: float,
|
||||
shared_controller: bool = False,
|
||||
) -> None:
|
||||
self.contracts = contracts
|
||||
self.approval_threshold = approval_threshold
|
||||
self.shared_controller = shared_controller
|
||||
|
||||
def scan(self, session, *, data_version_id=None) -> ScanOutcome:
|
||||
finding = detect_threshold_edge(self.contracts, self.approval_threshold)
|
||||
score = split_risk_score(finding, self.shared_controller)
|
||||
drafts: list[ClueDraft] = []
|
||||
if score > 0:
|
||||
drafts.append(
|
||||
ClueDraft(
|
||||
score=score,
|
||||
rationale=build_rationale(
|
||||
finding, self.approval_threshold, self.shared_controller
|
||||
),
|
||||
evidence={
|
||||
"near_threshold_contracts": [
|
||||
c.contract_id for c in finding.near_threshold
|
||||
],
|
||||
"edge_ratio": finding.ratio,
|
||||
"near_threshold_amount": finding.total_amount,
|
||||
"approval_threshold": self.approval_threshold,
|
||||
"shared_controller": self.shared_controller,
|
||||
},
|
||||
subjects={
|
||||
"customers": sorted(
|
||||
{c.customer_key for c in finding.near_threshold}
|
||||
)
|
||||
},
|
||||
amount_involved=finding.total_amount,
|
||||
)
|
||||
)
|
||||
return ScanOutcome(scanned_count=len(self.contracts), drafts=drafts)
|
||||
|
||||
Reference in New Issue
Block a user