- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
107 lines
4.3 KiB
Python
107 lines
4.3 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import UTC, datetime
|
|
from decimal import Decimal
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session, sessionmaker
|
|
from sqlalchemy.pool import StaticPool
|
|
|
|
from app.db.base import Base
|
|
from app.models.financial_record import ExpenseClaim
|
|
from app.services.hermes_risk_clue_collector import HermesRiskClueCollectorService
|
|
from app.services.risk_observations import RiskObservationService
|
|
|
|
|
|
def build_session_factory() -> sessionmaker[Session]:
|
|
engine = create_engine(
|
|
"sqlite+pysqlite:///:memory:",
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
)
|
|
Base.metadata.create_all(bind=engine)
|
|
return sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
|
|
|
|
|
def test_risk_clue_collector_outputs_review_packet_without_rule_writes() -> None:
|
|
forbidden_rule_execution = "执行" + "规则"
|
|
session_factory = build_session_factory()
|
|
with session_factory() as db:
|
|
claim = ExpenseClaim(
|
|
id="claim-risk-clue-1",
|
|
claim_no="RE-20260531090000-ABCDEFGH",
|
|
employee_name="张三",
|
|
department_name="销售部",
|
|
expense_type="travel",
|
|
reason="客户现场支持",
|
|
location="上海",
|
|
amount=Decimal("9800.00"),
|
|
currency="CNY",
|
|
invoice_count=2,
|
|
occurred_at=datetime(2026, 5, 30, 9, 0, tzinfo=UTC),
|
|
submitted_at=datetime(2026, 5, 31, 9, 0, tzinfo=UTC),
|
|
status="submitted",
|
|
approval_stage="财务审批",
|
|
risk_flags_json=[
|
|
{
|
|
"source": "rule_center",
|
|
"rule_code": "risk.travel.large_without_preapproval",
|
|
"label": "大额差旅缺少事前申请",
|
|
"message": "报销金额较高,未找到对应事前申请。",
|
|
"severity": "high",
|
|
}
|
|
],
|
|
)
|
|
db.add(claim)
|
|
db.flush()
|
|
|
|
RiskObservationService(db).upsert_observation(
|
|
{
|
|
"observation_key": "risk:claim-risk-clue-1:large_without_preapproval",
|
|
"subject_type": "expense_claim",
|
|
"subject_key": "claim:claim-risk-clue-1",
|
|
"subject_label": claim.claim_no,
|
|
"claim_id": claim.id,
|
|
"claim_no": claim.claim_no,
|
|
"risk_type": "preapproval_absent",
|
|
"risk_signal": "preapproval_absent",
|
|
"title": "大额差旅缺少事前申请",
|
|
"description": "报销金额较高,暂未匹配到事前申请,需要人工复核。",
|
|
"risk_score": 86,
|
|
"risk_level": "high",
|
|
"confidence_score": 0.82,
|
|
"source": "rule_center",
|
|
"contribution_scores": {"S_rule": 86},
|
|
"evidence": [
|
|
{
|
|
"source": "rule_center",
|
|
"title": "规则命中",
|
|
"detail": "金额 9800 元,缺少事前申请。",
|
|
}
|
|
],
|
|
"policy_refs": ["risk.travel.large_without_preapproval"],
|
|
}
|
|
)
|
|
db.commit()
|
|
|
|
packet = HermesRiskClueCollectorService(db).collect_risk_clues(run_id="run-risk-clue")
|
|
|
|
assert packet["task_type"] == "risk_clue_collect"
|
|
assert packet["writes_rules"] is False
|
|
assert packet["human_review_required"] is True
|
|
assert "主流程由外层智能体执行" in packet["role_boundary"]
|
|
assert forbidden_rule_execution not in packet["role_boundary"]
|
|
assert packet["fact_count"] == 1
|
|
assert packet["rule_hit_count"] >= 1
|
|
assert packet["risk_clue_count"] >= 1
|
|
assert packet["facts"][0]["claim_kind"] == "reimbursement"
|
|
assert packet["risk_clues"][0]["status"] == "human_review_required"
|
|
assert packet["risk_clues"][0]["observation_key"]
|
|
assert packet["risk_clues"][0]["feedback_status"] == "unreviewed"
|
|
assert packet["risk_clues"][0]["next_action"]
|
|
assert "recent" in packet["feedback_summary"]
|
|
assert packet["risk_clues"][0]["not_final_conclusion"] is True
|
|
serialized = str(packet)
|
|
assert "auto_publish" not in serialized
|
|
assert "candidate_risk_rules" not in serialized
|