feat: 迁移奶龙风控业务模块与演示文档
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
from datetime import datetime
|
||||
from types import MappingProxyType, SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
from app.core.contracts import RequestContext
|
||||
from app.model.fund import FundRiskAlert
|
||||
from app.repository.fund_query_repository import FundRecord
|
||||
from app.service.risk_analysis_service import RiskAnalysisService
|
||||
|
||||
|
||||
class FakeRepository:
|
||||
async def get_alert_detail(self, _alert_no: str) -> FundRecord:
|
||||
return FundRecord(
|
||||
entity="risk_alert_detail",
|
||||
values=MappingProxyType({
|
||||
"alert": {
|
||||
"alert_no": "ALERT-001",
|
||||
"alert_type": "适当性错配",
|
||||
"risk_level": "高",
|
||||
"rule_codes": ["RW-007"],
|
||||
"evidence_summary": "C2客户购买R5产品且留痕不完整。",
|
||||
"due_time": "2026-09-10T12:00:00",
|
||||
},
|
||||
"customer": {"customer_no": "CUST-001", "name": "张*", "risk_level": "C2"},
|
||||
"transaction": {"amount": "700000.00"},
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
class FakeSession:
|
||||
def __init__(self, alert: FundRiskAlert):
|
||||
self.alert = alert
|
||||
self.added = []
|
||||
self.committed = False
|
||||
|
||||
async def scalar(self, _statement):
|
||||
return self.alert
|
||||
|
||||
def add(self, value):
|
||||
self.added.append(value)
|
||||
|
||||
async def commit(self):
|
||||
self.committed = True
|
||||
|
||||
|
||||
class FailingResolver:
|
||||
async def resolve(self, **_kwargs):
|
||||
raise RuntimeError("模型端点不可用")
|
||||
|
||||
|
||||
class UnusedModelService:
|
||||
async def generate(self, *_args, **_kwargs):
|
||||
raise AssertionError("端点解析失败后不应调用模型")
|
||||
|
||||
|
||||
def context() -> RequestContext:
|
||||
return RequestContext(
|
||||
user_id="990000002",
|
||||
trace_id="analysis-trace",
|
||||
permissions=("risk:alert:read",),
|
||||
data_scope="all",
|
||||
)
|
||||
|
||||
|
||||
def alert() -> FundRiskAlert:
|
||||
return FundRiskAlert(
|
||||
id=1,
|
||||
alert_no="ALERT-001",
|
||||
customer_id=9,
|
||||
alert_type="适当性错配",
|
||||
alert_level="高",
|
||||
trigger_rule_codes=["RW-007"],
|
||||
evidence_summary="证据",
|
||||
evidence_snapshot={},
|
||||
priority_score=90,
|
||||
event_status="正在发生",
|
||||
status="待处理",
|
||||
ack_status="未确认",
|
||||
is_escalated=0,
|
||||
created_at=datetime(2026, 9, 10),
|
||||
updated_at=datetime(2026, 9, 10),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("output_type", "expected"),
|
||||
[
|
||||
("预警研判", "风险结论"),
|
||||
("回访话术", "开场说明"),
|
||||
("工单摘要", "工单标题"),
|
||||
],
|
||||
)
|
||||
async def test_analysis_fallback_contains_original_structure(
|
||||
output_type: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
session = FakeSession(alert())
|
||||
service = RiskAnalysisService(
|
||||
session,
|
||||
repository=FakeRepository(), # type: ignore[arg-type]
|
||||
model_service=UnusedModelService(),
|
||||
endpoint_resolver=FailingResolver(),
|
||||
)
|
||||
|
||||
result = await service.generate(context(), "ALERT-001", output_type)
|
||||
|
||||
assert expected in result["content"]
|
||||
assert result["source"] == "模板降级输出"
|
||||
assert session.alert.ai_analysis[output_type]["content"] == result["content"]
|
||||
assert session.committed is True
|
||||
assert session.added[0].action_type == "risk_ai_analysis_generated"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forbidden_model_claim_falls_back_to_template() -> None:
|
||||
class Resolver:
|
||||
async def resolve(self, **_kwargs):
|
||||
return [object()]
|
||||
|
||||
class ModelService:
|
||||
async def generate(self, *_args, **_kwargs):
|
||||
return SimpleNamespace(text="我已关闭预警")
|
||||
|
||||
session = FakeSession(alert())
|
||||
result = await RiskAnalysisService(
|
||||
session,
|
||||
repository=FakeRepository(), # type: ignore[arg-type]
|
||||
model_service=ModelService(),
|
||||
endpoint_resolver=Resolver(),
|
||||
).generate(context(), "ALERT-001", "预警研判")
|
||||
|
||||
assert result["source"] == "模板降级输出"
|
||||
assert "风险结论" in result["content"]
|
||||
Reference in New Issue
Block a user