feat: 添加可配置场景注册表与规则引擎

- 新增场景基类、配置加载、注册表与原语模块
- 添加 r10_refund_split 规则及场景 JSON Schema
- 扩展 scan 引擎与 scenarios API
- 新增场景注册表/配置/集成测试
- 更新前端 App、api、labels 支持新场景
This commit is contained in:
selfrelease
2026-06-17 09:57:02 +08:00
parent cbaa2156fc
commit 74e35fed96
20 changed files with 998 additions and 2 deletions
+76
View File
@@ -0,0 +1,76 @@
"""场景插件框架:统一的场景基类与产出物(A 打底)。
每个审计场景实现一个 BaseScenario 子类,自包含"检测→评分→产出线索草稿"
并通过 @register_scenario 注册。扫描调度器(engines/scan.run_scenarios)统一遍历
执行并落库,新增场景无需改动调度器、API 与前端。
"""
from __future__ import annotations
import uuid
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from sqlalchemy.orm import Session
@dataclass
class ClueDraft:
"""场景产出的线索草稿:仅承载"内容",落库由调度器统一完成。
风险域、场景码等元数据来自场景类属性;模型/数据版本由调度器注入。
"""
score: float
rationale: str
evidence: dict
subjects: dict = field(default_factory=dict)
amount_involved: float | None = None
title: str | None = None # 覆盖场景默认标题;为 None 时用 BaseScenario.title
@dataclass
class ScanOutcome:
"""单个场景一次扫描的结果:扫描覆盖数(证明全量性)+ 线索草稿列表。"""
scanned_count: int
drafts: list[ClueDraft] = field(default_factory=list)
class BaseScenario(ABC):
"""审计场景抽象基类。
子类通过类属性声明元数据(场景码、标题、风险域、前端展示名、阈值),
并实现 scan() 完成检测与评分。命中阈值的草稿由调度器统一生成线索。
"""
# 场景码(如 "R8"),全局唯一,注册表按此索引
code: str = ""
# 默认线索标题(草稿可覆盖)
title: str = ""
# 风险域(如 "收入"/"成本"
risk_domain: str = ""
# 前端展示名(替代 labels.ts 中写死的 scenarioLabel
label: str = ""
# 评分阈值:草稿 score >= 阈值才生成线索
score_threshold: float = 0.5
# 规则版本(配置驱动场景可用于追溯;代码场景一般为 None)
rule_version: str | None = None
@abstractmethod
def scan(
self,
session: Session,
*,
data_version_id: uuid.UUID | None = None,
) -> ScanOutcome:
"""执行检测与评分,返回扫描覆盖数与命中草稿。
Args:
session: 数据库会话(如需自取数)
data_version_id: 当前数据版本 ID(用于线索可追溯)
Returns:
ScanOutcomescanned_count 体现全量覆盖,drafts 为候选线索
"""
...