fix(expense): narrow travel route risk indicators
This commit is contained in:
@@ -160,6 +160,52 @@ def _add_vague_goods_rule_asset(
|
||||
)
|
||||
|
||||
|
||||
def _add_multi_city_reason_rule_asset(
|
||||
db: Session,
|
||||
manager: AgentAssetRuleLibraryManager,
|
||||
) -> None:
|
||||
rule_code = "risk.travel.medium.multi_city_no_reason"
|
||||
file_name = f"{rule_code}.json"
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"rule_code": rule_code,
|
||||
"name": "多城市行程缺少说明中风险",
|
||||
"evaluator": "multi_city_reason_required",
|
||||
"enabled": True,
|
||||
"requires_attachment": True,
|
||||
"applies_to": {
|
||||
"domains": ["expense", "travel"],
|
||||
"expense_types": ["travel"],
|
||||
"business_stages": ["reimbursement"],
|
||||
},
|
||||
"outcomes": {"fail": {"severity": "medium", "action": "manual_review"}},
|
||||
}
|
||||
manager.write_rule_library_json(
|
||||
library=RISK_RULES_LIBRARY,
|
||||
file_name=file_name,
|
||||
payload=payload,
|
||||
)
|
||||
db.add(
|
||||
AgentAsset(
|
||||
asset_type=AgentAssetType.RULE.value,
|
||||
code=rule_code,
|
||||
name="多城市行程缺少说明中风险",
|
||||
description="",
|
||||
domain=AgentAssetDomain.EXPENSE.value,
|
||||
scenario_json=["差旅费"],
|
||||
owner="pytest",
|
||||
status=AgentAssetStatus.ACTIVE.value,
|
||||
current_version="v1.0.0",
|
||||
published_version="v1.0.0",
|
||||
config_json={
|
||||
"detail_mode": "json_risk",
|
||||
"rule_library": RISK_RULES_LIBRARY,
|
||||
"rule_document": {"file_name": file_name},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _write_attachment_meta(storage_root, invoice_id: str, meta: dict[str, Any]) -> None:
|
||||
file_path = storage_root / invoice_id
|
||||
file_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
@@ -520,3 +566,78 @@ def test_vague_ticket_content_still_flags_unclear_goods_name(
|
||||
assert len(rule_flags) == 1
|
||||
assert rule_flags[0]["severity"] == "low"
|
||||
assert rule_flags[0]["evidence"]["matched_keywords"] == ["服务费"]
|
||||
|
||||
|
||||
def test_multi_city_reason_risk_marks_only_abnormal_route_items(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
with build_session() as db:
|
||||
manager = AgentAssetRuleLibraryManager(rule_root=tmp_path / "rules")
|
||||
storage_root = tmp_path / "attachments"
|
||||
_patch_rule_manager(monkeypatch, manager)
|
||||
monkeypatch.setattr(ExpenseClaimAttachmentStorage, "root", lambda self: storage_root)
|
||||
_add_multi_city_reason_rule_asset(db, manager)
|
||||
|
||||
claim = _build_claim(claim_no="RE-MULTI-CITY-001", expense_type="travel")
|
||||
claim.location = "上海"
|
||||
claim.reason = "支撑国网仿生产环境部署"
|
||||
routes = [
|
||||
("outbound", "武汉-上海", date(2026, 2, 20), Decimal("354.00")),
|
||||
("extra-out", "上海-深圳", date(2026, 2, 21), Decimal("438.00")),
|
||||
("extra-back", "深圳-上海", date(2026, 2, 22), Decimal("388.00")),
|
||||
("return", "上海-武汉", date(2026, 2, 23), Decimal("354.00")),
|
||||
]
|
||||
claim.items = [
|
||||
ExpenseClaimItem(
|
||||
item_date=item_date,
|
||||
item_type="train_ticket",
|
||||
item_reason=route,
|
||||
item_location=route,
|
||||
item_amount=amount,
|
||||
invoice_id=f"claim-multi-city/{suffix}.pdf",
|
||||
)
|
||||
for suffix, route, item_date, amount in routes
|
||||
]
|
||||
db.add(claim)
|
||||
db.commit()
|
||||
|
||||
for suffix, route, _, _ in routes:
|
||||
_write_attachment_meta(
|
||||
storage_root,
|
||||
f"claim-multi-city/{suffix}.pdf",
|
||||
{
|
||||
"document_info": {
|
||||
"document_type": "train_ticket",
|
||||
"document_type_label": "火车票",
|
||||
"scene_code": "travel",
|
||||
"scene_label": "交通票据",
|
||||
"fields": [
|
||||
{"key": "route", "label": "路线", "value": route},
|
||||
],
|
||||
},
|
||||
"ocr_summary": f"火车票;{route}",
|
||||
"ocr_text": f"旅客行程为 {route}",
|
||||
},
|
||||
)
|
||||
|
||||
review = ExpenseClaimService(db).evaluate_platform_risk_rules(
|
||||
claim,
|
||||
business_stage="reimbursement",
|
||||
)
|
||||
rule_flags = [
|
||||
flag
|
||||
for flag in review["flags"]
|
||||
if isinstance(flag, dict)
|
||||
and flag.get("rule_code") == "risk.travel.medium.multi_city_no_reason"
|
||||
]
|
||||
|
||||
assert len(rule_flags) == 1
|
||||
flagged_item_ids = set(rule_flags[0]["item_ids"])
|
||||
route_item_ids = {item.item_reason: item.id for item in claim.items}
|
||||
assert flagged_item_ids == {
|
||||
route_item_ids["上海-深圳"],
|
||||
route_item_ids["深圳-上海"],
|
||||
}
|
||||
assert route_item_ids["武汉-上海"] not in flagged_item_ids
|
||||
assert route_item_ids["上海-武汉"] not in flagged_item_ids
|
||||
|
||||
Reference in New Issue
Block a user