# LeAudit Platform — 系统现状总览

> 最后更新：2026-04-28

## 一、目标架构

补充文档：

- 并发与重试参数：`docs/leaudit/并发与重试参数说明.md`

```
┌─ API ───────────────────────────────────────────────────────────┐
│  AuditController (/audit)          RuleController (/rule-sets)   │
│  DocumentController (/upload)                                  │
│  POST /run      触发评查           GET  /              规则集列表 │
│  POST /upload   上传建档/可选自动评查                            │
│  GET  /run/:id  查询状态           GET  /{type}/versions 版本列表 │
│  GET  /result/:id 查询结果         GET  /versions/:id/content 正文│
│                                    POST /{type}/validate  校验   │
│                                    POST /{type}/versions  创建   │
│                                    POST /{type}/publish   发布   │
│                                    POST /{type}/rollback  回滚   │
│                                    GET  /bindings        绑定列表 │
│                                    POST /{type}/bindings 创建绑定│
│                                    PUT  /bindings/{id}   更新绑定│
│                                    DELETE /bindings/{id} 删除绑定│
├─ Service ───────────────────────────────────────────────────────┤
│  AuditServiceImpl                RuleServiceImpl + OssServiceImpl│
├─ Bridge ────────────────────────────────────────────────────────┤
│  FileSourceResolver  RuleVersionResolver  RulesLoader            │
│  AuditCtxBuilder     AuditServiceFactory  NativeRunner           │
│  StorageAdapter      ResultAdapter        RuleValidator          │
│  tasks.py (dispatch_leaudit_task)                                │
├─ leaudit (不改) ────────────────────────────────────────────────│
│  AuditCtx → AuditService.audit() → OCR/Normalize/Extract/       │
│  Evaluate/Rescue → 填充 ctx.normalized_doc/extraction/           │
│  evaluation/fallback_tasks/timing/extraction_errors              │
├─ 存储 ──────────────────────────────────────────────────────────┤
│  OSS (MinIO)                        PostgreSQL                  │
│  bdocs/{region}/{type}/{doc_id}/    leaudit_documents           │
│  rules/{rule_type}/{version_no}/    leaudit_document_files      │
│  artifacts/{region}/{run_id}/       leaudit_audit_runs          │
│                                     leaudit_rule_sets           │
│                                     leaudit_rule_versions       │
│                                     leaudit_rule_type_bindings  │
│                                     leaudit_rule_results        │
│                                     leaudit_field_results       │
│                                     leaudit_run_metrics         │
│                                     leaudit_run_errors          │
│                                     leaudit_rescue_outcomes     │
│                                     leaudit_artifacts           │
└─────────────────────────────────────────────────────────────────┘
```

## 二、两条核心数据流

### 流 A：规则生命周期

```
编辑 YAML
  → POST /rule-sets/{type}/validate → RuleValidator (YAML语法 + DSL语义)
  → POST /rule-sets/{type}/versions → 上传 OSS + INSERT leaudit_rule_versions
  → POST /rule-sets/{type}/publish  → UPDATE leaudit_rule_sets.current_version_id
  → POST /rule-sets/{type}/bindings → INSERT leaudit_rule_type_bindings
  → 新 run 自动绑定新版本到对应文档类型
```

### 流 B：评查执行

```
POST /audit/run { documentId }
  → AuditServiceImpl.Run()
    1. 查 leaudit_documents + leaudit_document_files
    2. 查 leaudit_rule_type_bindings → rule_set_id + rule_version_id
    3. INSERT leaudit_audit_runs (rule_version_id, rule_source_oss_url, sha256)
    4. FileSourceResolver → 下载文档 bytes
    5. dispatch_leaudit_task()
       ┌─ RuleVersionResolver → OSS 下载规则 YAML → SHA256 校验
       ├─ RulesLoader → RulesFile
       ├─ NativeRunner.run() → AuditCtx → AuditService.audit()
       └─ NativeRunner.persist_result()
          ├─ save_ocr_result()         → leaudit_artifacts
          ├─ save_extraction_result()  → leaudit_field_results
          ├─ save_evaluation_results() → leaudit_rule_results + 分数
          ├─ save_run_errors()         → leaudit_run_errors
          ├─ save_rescue_outcomes()    → leaudit_rescue_outcomes
          ├─ save_run_metrics()        → leaudit_run_metrics
          └─ finalize_run()            → 终态 (result_status/finished_at)
```

## 三、模块完成度

### M1：OSS 基础设施 — 100%

| 文件 | 状态 | 说明 |
|---|---|---|
| `fastapi_common/fastapi_common_storage/oss_client.py` | 已完成 | 上传/下载/Presign/ObjectExists |
| `fastapi_common/fastapi_common_storage/oss_path_utils.py` | 已完成 | BuildBusinessDocKey/ArtifactKey/RuleYamlKey |
| `fastapi_admin/config/_settings.py` | 已完成 | OSS_ENDPOINT/ACCESS_KEY/SECRET_KEY/BUCKET/REGION/USE_SSL/PRESIGN_EXPIRE |
| `fastapi_admin/config/__init__.pyi` | 已完成 | 类型声明 |

### M2：规则管理后端 — 100%

| 文件 | 状态 | 说明 |
|---|---|---|
| `controllers/ruleController.py` | 已完成 | 11 个端点（含绑定管理） |
| `services/ruleService.py` | 已完成 | IRuleService 接口 |
| `services/impl/ruleServiceImpl.py` | 已完成 | 规则 CRUD + 绑定 CRUD |
| `leaudit_bridge/ruleValidator.py` | 已完成 | YAML 语法 + DSL 语义校验 |
| `domian/Dto/ruleVersionCreateDto.py` | 已完成 | 创建版本 DTO |
| `domian/Dto/ruleValidateDto.py` | 已完成 | 校验 DTO |
| `domian/Dto/rulePublishDto.py` | 已完成 | 发布/回滚 DTO |
| `domian/Dto/ruleBindingDto.py` | 已完成 | 绑定 DTO |
| `domian/vo/ruleVo.py` | 已完成 | RuleSetVO/VersionVO/ContentVO/ValidationVO/BindingVO |

### M3：执行链与持久化 — ~95%

| 文件 | 状态 | 说明 |
|---|---|---|
| `leaudit_bridge/auditCtxBuilder.py` | 已完成 | 构建原生 AuditCtx |
| `leaudit_bridge/auditServiceFactory.py` | 已完成 | 创建 AuditService |
| `leaudit_bridge/nativeRunner.py` | 已完成 | 原生执行入口 + persist_result |
| `leaudit_bridge/fileSourceResolver.py` | 已完成 | 文档来源解析（接 OSS） |
| `leaudit_bridge/ruleVersionResolver.py` | 已完成 | 规则版本解析（含 SHA256） |
| `leaudit_bridge/storage_adapter.py` | 已完成 | 全部持久化方法（已修双重 finalize） |
| `leaudit_bridge/tasks.py` | 已完成 | 任务入口 + 失败处理 |
| `leaudit_bridge/pipeline.py` | 待退役 | 旧管线（已被 nativeRunner 替代） |
| `leaudit_bridge/client_factory.py` | 90% | OCR/LLM/VLM 客户端工厂 |

### M4：全流程联调 — ~25%

| 项目 | 状态 |
|---|---|
| 上传入口梳理 | 已完成 |
| 上传后自动触发评查 | 已完成最小链路（`POST /upload` + `autoRun`） |
| 结果查询展示 | 已补运行/规则/字段/errors/rescue/metrics/artifacts 查询 |
| 联调样例准备 | 待做 |
| E2E 验证 | 待做 |

## 四、持久化执行顺序与终态

`persist_result()` 严格按以下顺序执行，`finalize_run` 在最后是唯一的终态写入者：

```
save_ocr_result()
save_extraction_result()
save_evaluation_results()    ← 只写分数/计数，不写 finished_at
save_run_errors()
save_rescue_outcomes()
save_run_metrics()
finalize_run()               ← 唯一写 result_status / finished_at / rescue_applied / phase
```

**终态来源（直接从 AuditCtx 读取，不另存平行状态）：**

| 平台表 | 来源 |
|---|---|
| `leaudit_run_metrics` | `ctx.timing` |
| `leaudit_run_errors` | `ctx.extraction_errors` + `ctx.extraction.all_errors` |
| `leaudit_rescue_outcomes` | `ctx.fallback_tasks` |
| `leaudit_audit_runs.result_status` | 综合 `ctx.fallback_tasks` + `ctx.evaluation` 推导 |
| `leaudit_audit_runs.finished_at` | `finalize_run` 写入 now() |
| `leaudit_audit_runs.rescue_applied` | `bool(ctx.fallback_tasks)` |

## 四点五、最新补充：上传建档入口

当前仓库已补最小上传入口：

```text
POST /upload (multipart/form-data)
  file
  typeId / typeCode
  region=default
  fileRole=primary
  createdBy?
  autoRun=false
  speed=normal|urgent
```

执行链：

```text
Upload
  -> DocumentServiceImpl.Upload()
  -> create leaudit_documents
  -> 旧 active 文件失效
  -> 上传原始文件到 OSS:
     bdocs/{region}/{type_code}/{document_id}/v{n}/{file_role}.{ext}
  -> INSERT leaudit_document_files
  -> autoRun=true 时直接调用 AuditServiceImpl.Run()
```

说明：

- `leaudit_documents` 现阶段是平台内部文档主表，不再依赖旧系统 `documents.id`
- 每次前端上传都会新建一条 `leaudit_documents`
- `speed`
  - `normal` -> `leaudit.normal`
  - `urgent` -> `leaudit.urgent`

## 四点六、最新补充：结果查询视图

当前 `GetRunStatus()` / `GetResult()` 已不再只返回 run 主表摘要。

- `GetRunStatus()` 当前已返回：
  - `documentFileId`
  - `resultStatus`
  - `ruleSetId` / `ruleVersionId` / `ruleTypeId`
  - `rescueApplied`
  - `skippedCount`
- `GetResult()` 当前已可聚合查询：
  - `leaudit_rule_results`
  - `leaudit_field_results`
  - `leaudit_run_errors`
  - `leaudit_rescue_outcomes`
  - `leaudit_run_metrics`
  - `leaudit_artifacts`

建议联调方式：

- 上传后从 `run.runId` 轮询 `GET /audit/run/{runId}`
- 完成后调用 `GET /audit/result/{runId}`
- worker 日志里会明确打印：
  - 已投递到哪个队列
  - worker 实际消费的是 `urgent` 还是 `normal`

## 四点七、关键关系图：DocumentType -> Binding -> RuleSet -> RuleVersion

这一段是当前规则执行链里最关键的一层路由关系。

很多时候容易把这四张表混在一起，但它们职责其实完全不同：

- `leaudit_document_types`：定义“系统里有哪些文档类型”
- `leaudit_rule_type_bindings`：定义“某个文档类型该用哪套规则集”
- `leaudit_rule_sets`：定义“某类规则集合本身”
- `leaudit_rule_versions`：定义“某套规则的具体版本快照”

### 关系图

```text
┌────────────────────────────┐
│ leaudit_document_types     │
│----------------------------│
│ id                         │
│ code                       │ <- 文档类型编码，如 contract.entrust
│ name                       │
│ extraction_mode            │
│ is_enabled                 │
└──────────────┬─────────────┘
               │ 1:N
               │
               ▼
┌────────────────────────────┐
│ leaudit_rule_type_bindings │
│----------------------------│
│ id                         │
│ doc_type_id                │ -> leaudit_document_types.id
│ doc_type_code              │ -> 冗余编码，便于快速排查
│ rule_set_id                │ -> leaudit_rule_sets.id
│ binding_mode               │ -> explicit / wildcard / fallback
│ priority                   │ -> 多条命中时按优先级选
│ is_active                  │
│ region                     │ -> default / mz / yf / ...
└──────────────┬─────────────┘
               │ N:1
               │
               ▼
┌────────────────────────────┐
│ leaudit_rule_sets          │
│----------------------------│
│ id                         │
│ rule_type                  │ <- 与 DSL metadata.type_id 对齐
│ rule_name                  │
│ current_version_id         │ -> 当前生效版本
│ status                     │
└──────────────┬─────────────┘
               │ 1:N
               │
               ▼
┌────────────────────────────┐
│ leaudit_rule_versions      │
│----------------------------│
│ id                         │
│ rule_set_id                │ -> leaudit_rule_sets.id
│ version_no                 │
│ status                     │
│ oss_url                    │ -> rules/.../rules.yaml
│ file_sha256                │
│ metadata_type_id           │
│ published_at               │
└────────────────────────────┘
```

### 执行时怎么走

真正运行评查时，不是直接“拿一个 rule_type 去找 YAML”，而是按下面这条链走：

```text
leaudit_documents.type_id
  -> leaudit_document_types.id
  -> leaudit_rule_type_bindings.doc_type_id
  -> leaudit_rule_type_bindings.rule_set_id
  -> leaudit_rule_sets.current_version_id
  -> leaudit_rule_versions.id
  -> leaudit_rule_versions.oss_url
  -> 下载对应 rules.yaml
  -> NativeRunner / AuditService.audit(ctx)
```

### 每张表到底解决什么问题

#### 1. `leaudit_document_types`

它解决的是：

```text
“这份文档属于什么类型？”
```

例如：

```text
id = 2
code = contract.entrust
name = 通用委托合同
```

那一条 `leaudit_documents` 记录里如果：

```text
type_id = 2
```

就表示这份文档被平台认定为“通用委托合同”。

它本质上是平台的“文档分类字典表”。

#### 2. `leaudit_rule_type_bindings`

它解决的是：

```text
“这种文档类型，应该使用哪套规则集？”
```

例如：

```text
doc_type_id = 2
rule_set_id = 22
region = default
binding_mode = explicit
priority = 100
```

意思就是：

- 文档类型 `2`（通用委托合同）
- 在 `default` 区域
- 显式绑定到规则集 `22`

这张表本质上是“规则路由表”。

它不能省略，因为后续扩展会依赖它支持：

- 同一种文档类型在不同地区走不同规则
- 同一种文档类型绑定多套规则并通过优先级决策
- fallback / wildcard 兜底规则

#### 3. `leaudit_rule_sets`

它解决的是：

```text
“系统里存在什么规则集合？”
```

例如：

```text
id = 22
rule_type = contract.entrust
rule_name = 通用委托合同
current_version_id = 2
```

这表示：

- 系统里有一套“通用委托合同”规则集
- 当前生效版本是 `2`

注意：

- `rule_set` 表示“规则集合”的稳定身份
- 它不是某个具体 YAML 版本文件

#### 4. `leaudit_rule_versions`

它解决的是：

```text
“这套规则当前/历史具体长什么样？”
```

例如：

```text
id = 2
rule_set_id = 22
version_no = 2.0
oss_url = rules/contract.entrust/2.0/rules.yaml
status = published
```

这表示：

- 规则集 `22`
- 的 `2.0` 版本
- 对应 OSS 上某个 `rules.yaml`

这层是规则版本治理的核心，支持：

- 发布
- 回滚
- 历史追溯
- 运行时精确复盘

### 为什么一定要拆成四层

如果不拆，后面会遇到很多问题：

- 文档类型和规则类型耦合死，无法独立演进
- 没法优雅支持一类文档多套规则
- 没法支持地区差异
- 没法做发布 / 回滚 / 历史 run 溯源

拆成四层以后，结构会更稳定：

- `document_type` 负责“分类”
- `binding` 负责“路由”
- `rule_set` 负责“规则身份”
- `rule_version` 负责“规则版本快照”

### 当前初始化策略

基于当前数据库里已有的 `leaudit_rule_sets`，当前最合理的初始化策略是：

```text
rule_set.rule_type  -> document_type.code
rule_set.rule_name  -> document_type.name
document_type.id    -> binding.doc_type_id
rule_set.id         -> binding.rule_set_id
rule_set.current_version_id -> 当前执行版本
```

也就是说，先用现有 `rule_set` 反向补齐：

- `leaudit_document_types`
- `leaudit_rule_type_bindings`

这是当前最稳、也最方便后续扩展的做法。

## 五、数据库表关系

```
leaudit_document_types
  └── leaudit_rule_type_bindings (doc_type_id → rule_set_id)
        └── leaudit_rule_sets (current_version_id)
              └── leaudit_rule_versions (oss_url, sha256)
                    └── leaudit_audit_runs (rule_version_id, rule_source_oss_url)
                          ├── leaudit_rule_results
                          ├── leaudit_field_results
                          ├── leaudit_run_metrics
                          ├── leaudit_run_errors
                          ├── leaudit_rescue_outcomes
                          └── leaudit_artifacts
```

## 六、关键设计原则

1. **leaudit 核心不改** — 所有定制在 bridge 层，leaudit 保持纯 Python 包
2. **只读 AuditCtx** — 执行完只从 ctx 上读取，不自己模拟 stage
3. **规则版本溯源** — 每个 run 绑定具体 `rule_version_id`，老 run 不受新发布影响
4. **OSS 真源 + DB 索引** — YAML 正文存 OSS，元数据索引存 PostgreSQL
5. **终态单点写入** — `finalize_run` 是 run 主表终态的唯一写入者
6. **独立 session 提交** — 每个 `save_*` 独立会话，按"先子表后主表"顺序保证最终一致性

## 七、已知待补项

| 缺口 | 优先级 |
|---|---|
| `run_metrics.llm_call_count` / `vlm_call_count` 仍为空 | P1（可从 RescueTask 汇总） |
| `ctx.timing` 缺 normalize/rescue 独立 key | P2（等 leaudit 原生补） |
| 文档状态更新逻辑分散在两处 | P1（应收敛到 StorageAdapter） |
| Celery 异步化 | P2（当前同步可跑通，生产需异步） |
| `_TYPE_ID_RULES_MAP` 硬编码兜底 | P2（等 bindings 全覆盖后移除） |
| M4 E2E 联调 | P0（下个里程碑） |
