docs: reorganize by module

This commit is contained in:
wren
2026-05-09 20:04:08 +08:00
parent 29873eaecd
commit c9d7a693b8
23 changed files with 2161 additions and 197 deletions
@@ -0,0 +1,73 @@
# 旧规则绑定表下线观察与删表草案
## 当前策略
- 新主链路:`leaudit_rule_group_bindings`
- 旧兼容表:`leaudit_rule_type_bindings`
- 当前仅保留 `UpdateBinding / DeleteBinding` 对历史 `BindingId` 的 fallback
## 已完成
- 文档类型保存不再双写旧表
- `CreateBinding` 不再 fallback 写旧表
- 规则绑定主读链路优先读取新分组绑定
## 观察期要看什么
### 1. 后端日志
当前已经在以下场景增加明确日志:
- `rule binding legacy fallback hit on update`
- `rule binding legacy fallback hit on delete`
如果观察期内不再出现以上日志,说明已经基本没有历史旧 `BindingId` 命中。
### 2. 数据检查
执行:
```bash
psql ... -f scripts/precheck_drop_legacy_rule_type_bindings.sql
```
重点看:
- `doc_types_legacy_only = 0`
- 新链路绑定明细完整
- 旧表活动绑定仅剩历史冗余数据
## 建议收口顺序
1. 保持现状进入观察期
2. 连续一段时间无 fallback 日志
3. 删除 `UpdateBinding / DeleteBinding` 的旧表 fallback
4. 再执行删表 SQL
## 删表前条件
- 前端主链路全部走评查点分组绑定接口
- 不再有外部脚本调用旧 `/api/rule-sets/bindings/{bindingId}` 历史 ID
- 观察期日志为 0
- `doc_types_legacy_only = 0`
## 删表 SQL 草案
```sql
BEGIN;
DROP TABLE IF EXISTS leaudit_rule_type_bindings;
COMMIT;
```
## 回滚草案
如果只是删 fallback 代码但还没删表,直接回滚代码即可。
如果已经删表,必须依赖:
- 事先数据库备份
- 或按历史 DDL 重建表结构后再回灌数据
因此删表动作必须放在数据库备份之后执行。
@@ -0,0 +1,336 @@
# 评查点分组、文档类型与规则集目标结构与迁移方案
更新时间:2026-05-03
## 1. 产品确认后的最终口径
最终目标结构统一为:
- 一级分组 = 业务大类
- 例如:`合同``卷宗`
- 后续若出现新的入口业务,也允许继续新增一级分组
- 二级分组 = 该业务大类下的具体业务类型
- 例如合同下:`建设工程合同``买卖合同`
- 例如卷宗下:`处罚-一般程序``许可-停业办理`
- 规则集 = 挂在二级分组下
- 入口模块 = 绑定一级分组
补充关系口径:
- 文档类型 = 具体业务类型主数据
- 文档类型页 = 主数据维护与汇总展示
- 评查点分组页 = 实际运行绑定维护页
这套结构对应的运行链路是:
- 入口模块
- 一级分组(业务大类)
- 二级分组(具体业务类型)
- 规则集
- 规则版本
其中:
- 入口模块:决定当前页面进入的是哪条业务线
- 一级分组:承接该业务线的一级分类容器
- 二级分组:决定这次上传/评查具体命中的业务类型
- 规则集:决定跑哪组规则
- 规则版本:决定跑规则集的哪一版
也就是说,当前正式口径是:
`入口模块 -> 一级分组(业务大类) -> 二级分组(具体业务类型) -> 规则集 -> 规则版本`
文档类型在这条链路中的位置是:
- 作为“具体业务类型主数据”
- 通过 `document_type_id` 与二级分组形成稳定对应关系
- 用于上传页、规则页、文档列表等业务页面识别当前业务类型
## 1.1 老系统与新系统的关系
### 老系统(docauditai
老系统更接近:
```text
DocumentType -> TopLevelGroup(s) -> ChildGroups -> EvaluationPoints
```
特点:
- `document_types.evaluation_point_groups_ids` 绑定顶层组
- `evaluation_point_groups` 是两级树
- `evaluation_points` 实际挂在子组下
- 运行时还会按 `area + document_attribute_type` 过滤
### 当前新平台(leaudit-platform
当前新平台已经同时存在两套表达:
1. 规则执行主链
- `DocumentType -> RuleTypeBinding -> RuleSet -> RuleVersion`
2. 分组管理主链
- `EntryModule -> Level1Group -> Level2Group -> RuleSet`
这就是大家容易混淆的根源。
### 当前统一理解
应统一理解为:
- 文档类型是业务类型主数据
- 二级分组是运行时具体命中的业务类型节点
- 规则集挂在二级分组下
- `leaudit_rule_type_bindings` 继续承担执行链快速查找规则集的职责
也就是说:
- 文档类型页负责“主数据”
- 分组页负责“运行绑定”
- `rule_type_bindings` 负责“执行收口”
## 2. 现在系统为什么会让人混淆
当前系统的过渡态更接近:
- 一级分组 = 具体文档类型
- 如:`建设工程合同``停业办理`
- 二级分组 = 该文档类型下的默认子类型
- 如:`通用`
这与产品最新口径不一致,主要有三个问题:
1. 一级分组被错误地做成了“具体文档类型”,而不是“业务大类容器”。
2. 上传页看到的“子类型”其实只是默认二级分组,业务人员会误以为配置还没做好。
3. 入口模块与一级分组之间缺少明确绑定语义,后续无法优雅支持新入口。
## 3. 目标树形样例
### 3.1 合同类
```text
合同(一级 = 业务大类)
├── 建设工程合同(二级 = 具体业务类型)
│ ├── 规则集:建设工程合同-正文审查
│ └── 规则集:建设工程合同-签署审查
├── 买卖合同(二级 = 具体业务类型)
│ └── 规则集:买卖合同-通用审查
└── 委托合同(二级 = 具体业务类型)
└── 规则集:委托合同-通用审查
```
### 3.2 卷宗类
```text
卷宗(一级 = 业务大类)
├── 处罚-一般程序(二级 = 具体业务类型)
│ └── 规则集:行政处罚-一般程序审查
├── 处罚-简易程序(二级 = 具体业务类型)
│ └── 规则集:行政处罚-简易程序审查
├── 许可-停业办理(二级 = 具体业务类型)
│ └── 规则集:行政许可-停业办理审查
└── 许可-新办办理(二级 = 具体业务类型)
└── 规则集:行政许可-新办办理审查
```
### 3.3 将来新增入口
```text
内部公文(一级 = 业务大类)
├── 请示类公文(二级)
├── 通知类公文(二级)
└── 纪要类公文(二级)
```
即:
- 不需要预先写死“只有合同、卷宗”
- 后续新增业务时,直接新建一级、新建二级、挂规则集、再把一级绑定给入口模块即可
## 4. 页面职责重定义
### 4.1 评查点分组页 `/rule-groups`
这是唯一的运行绑定页,负责:
- 维护一级分组(业务大类)
- 维护二级分组(具体业务类型)
- 维护二级分组绑定的规则集
- 维护一级分组与入口模块的绑定关系
统一文案:
- 一级:业务大类
- 二级:具体业务类型
- 规则集:实际运行绑定
- 入口模块:一级分组归属入口
### 4.2 文档类型页 `/document-types`
文档类型页保留为“类型主数据页”,职责调整为:
- 维护具体文档类型名称、编码、描述
- 展示该类型的汇总规则集
- 不再承担一级/二级分组结构维护职责
统一文案:
- “汇总规则集”
- “仅用于总览,实际运行绑定请在评查点分组页维护”
补充说明:
- 当前文档类型与规则集之间仍可保留“汇总绑定 / 快速绑定”能力
- 但最终运行时应以“二级分组绑定规则集”为准
- 因此文档类型页更适合做:
- 主数据编辑
- 入口模块归属展示
- 汇总规则集总览
### 4.3 上传页 `/files/upload`
上传页统一交互为:
1. 先根据入口模块确定可用的一级分组
2. 再根据一级分组或文档类型加载二级分组
3. 用户选择本次上传实际命中的二级分组
4. 最终上传时携带 `groupId`
## 5. 数据模型建议
## 5.1 继续复用 `leaudit_evaluation_point_groups`
当前表不必推倒重建,建议继续复用,并明确字段语义:
- `pid = 0`
- 表示一级分组(业务大类)
- `pid != 0`
- 表示二级分组(具体业务类型)
字段语义建议:
- 一级分组:
- `document_type_id` 可为空
- `entry_module_id` 可为空,但新模型建议填写
- `name` = 业务大类名称,如 `合同` / `卷宗`
- 二级分组:
- `document_type_id` 建议必填,对应实际具体文档类型
- `pid` 指向对应一级分组
- `entry_module_id` 通常从一级继承,不需要单独维护
## 5.2 新增或正式启用 `entry_module_id`
为支撑“一级分组绑定入口模块”,分组表需要显式支持:
- `entry_module_id`
用途:
- 绑定一级分组与入口模块的归属关系
- 后续上传页、首页导航、入口权限控制都可以围绕该关系展开
## 5.3 与 `leaudit_rule_type_bindings` 的关系
当前不建议粗暴删除 `leaudit_rule_type_bindings`
更合理的口径是:
- 分组树负责表达“业务结构”和“规则集挂载关系”
- `leaudit_rule_type_bindings` 负责表达“执行时文档类型如何快速命中规则集”
可以把它理解为:
- 分组树 = 业务真相源
- `rule_type_bindings` = 执行加速索引 / 收口层
## 6. 迁移原则
### 6.1 旧“一级=具体文档类型”是过渡态
当前数据库里已经有一批:
- 一级 = `建设工程合同`
- 二级 = `通用`
这些不是最终结构,只是为了先把运行链路跑通。
### 6.2 未来迁移目标
应逐步迁成:
- 一级 = `合同`
- 二级 = `建设工程合同`
- 规则集继续挂二级
也就是说,未来需要把“当前一级里的具体文档类型”下沉成真正的二级。
## 7. 推荐迁移步骤
### 步骤 1:冻结语义
先统一团队口径:
- 一级 = 业务大类
- 二级 = 具体业务类型
- 规则集 = 只挂二级
- 一级 = 入口模块绑定对象
### 步骤 2:后端先支持双语义兼容
后端接口先支持:
- 一级可绑定 `entry_module_id`
- 二级继续绑定 `document_type_id`
- `/by-document-types` 允许从“二级文档类型”反查所属一级
这样即使数据库还没完全迁,也能先把接口语义准备好。
### 步骤 3:前端页面改口径
`/rule-groups` 需逐步改为:
- 一级显示业务大类
- 二级显示具体业务类型
- 页面突出一级与入口模块关系
### 步骤 4:测试库迁移旧树
建议先在测试库按以下思路演练:
1. 建立一级:`合同`
2. 建立一级:`卷宗`
3. 将现有“建设工程合同 / 买卖合同 / 停业办理 ...”迁成各自二级
4. 把原二级下规则集保留在新二级
5. 清理旧默认 `通用` 子类型,仅在确实没有继续细分的情况下保留
### 步骤 5:再落正式库
正式库迁移必须在测试通过后执行,避免规则命中错位。
## 8. 运行时唯一正确规则
迁移完成后,唯一正确的运行规则应为:
1. 用户进入某入口模块
2. 系统确定该入口可见的一级分组
3. 用户选择具体二级分组
4. 后端按 `groupId` 查询 `leaudit_rule_group_bindings`
5. 找到对应规则集
6. 取规则集可执行版本执行评查
即:
- 文档类型页:总览
- 分组页:运行绑定
- 上传页:按二级分组命中
## 9. 当前开发阶段结论
截至当前:
- “规则集挂二级”这部分方向是对的
- “一级=具体文档类型”这部分已被产品新口径推翻
- 后续开发应转向“一级=业务大类、一级绑定入口、二级=具体业务类型”
因此接下来所有实现都应围绕这条新口径继续推进,而不是再沿旧过渡模型加深。
@@ -0,0 +1,379 @@
# 评查点分组迁移执行前检查清单
更新时间:2026-05-03
## 1. 目标
本清单用于在正式执行 `scripts/migrate_rule_groups_to_doc_type_roots.sql` 之前,先把当前库里的旧数据、兼容态数据和潜在冲突点查清楚。
如果希望直接执行一份只读巡检 SQL,可使用:
- `scripts/precheck_rule_group_migration.sql`
本次迁移的目标仍然是:
- 一级分组 = 业务大类
- 二级分组 = 具体业务类型
- 规则集 = 挂在二级分组下
- 入口模块 = 绑定一级分组
## 2. 当前风险结论
正式迁移前,必须先确认下面 4 类风险:
1. 是否仍存在“一级直接挂具体文档类型”的旧根数据。
2. 是否存在同一个 `document_type_id` 被多个二级分组同时承接的情况。
3. 是否存在规则集同时挂在旧一级根和旧默认子级上,导致迁移后重复。
4. `document_types.entry_module_id` 是否足够可靠,能支撑“先按入口模块粗分到一级业务大类”。
如果这 4 类风险没有检查清楚,不应直接跑迁移脚本。
## 2.1 当前已知巡检结论(2026-05-03
基于本次实际巡检结果:
- `leaudit_document_types` 共 20 条,入口归属完整
- 合同管理:10 条
- 案卷智能评查:10 条
- 当前仍有 20 个旧一级根
- 即:`一级 = 具体文档类型`
- 当前已经存在 2 个新一级业务大类根
- `root.contract -> 合同`
- `root.casefile -> 行政卷宗`
- 当前有 20 个默认二级分组
- 即:`二级 = 通用 / *.default`
- 当前没有发现明显冲突:
- 没有“同一文档类型被多个二级分组重复承接”
- 没有“同一规则集同时挂在旧一级根和默认子级”
- 没有“二级分组未绑定规则集”
结论:
- 当前库已经具备正式迁移的基础条件
- 但还没有完成最后的结构补齐
- 正式迁移前仍要先补 `entry_module_id`,再重跑巡检
## 3. 必查项
### 3.1 一级旧根检查
确认当前是否还有“一级 = 具体文档类型”的历史根:
```sql
SELECT
id,
code,
name,
document_type_id,
entry_module_id,
sort_order,
is_enabled
FROM leaudit_evaluation_point_groups
WHERE deleted_at IS NULL
AND COALESCE(pid, 0) = 0
AND document_type_id IS NOT NULL
ORDER BY id;
```
判定原则:
- 查出来的这些记录,都属于兼容态旧根。
- 正式迁移后,它们不应再继续作为最终一级分组存在。
### 3.2 一级业务大类根检查
确认当前是否已经存在真正的业务大类根:
```sql
SELECT
id,
code,
name,
entry_module_id,
sort_order,
is_enabled
FROM leaudit_evaluation_point_groups
WHERE deleted_at IS NULL
AND COALESCE(pid, 0) = 0
AND document_type_id IS NULL
ORDER BY sort_order, id;
```
判定原则:
- 正常目标结构下,这里应该看到如 `合同``卷宗` 这类一级业务大类。
- 如果这里为空,说明当前库还没有真正切到目标结构。
### 3.3 二级分组唯一性检查
确认同一个文档类型是否被多个二级分组重复承接:
```sql
SELECT
document_type_id,
COUNT(*) AS child_count,
STRING_AGG(name, ' / ' ORDER BY id) AS child_names
FROM leaudit_evaluation_point_groups
WHERE deleted_at IS NULL
AND COALESCE(pid, 0) <> 0
AND document_type_id IS NOT NULL
GROUP BY document_type_id
HAVING COUNT(*) > 1
ORDER BY document_type_id;
```
判定原则:
- 如果结果为空,说明每个文档类型目前最多只挂到一个二级分组,迁移最安全。
- 如果结果不为空,要逐条判断:
- 是合理的“一个文档类型拆成多个业务子类型”
- 还是历史脏数据
### 3.4 旧默认子级检查
确认当前是否存在旧默认子级:
```sql
SELECT
child.id,
child.pid,
parent.name AS parent_name,
child.name,
child.code,
child.document_type_id
FROM leaudit_evaluation_point_groups child
JOIN leaudit_evaluation_point_groups parent
ON parent.id = child.pid
AND parent.deleted_at IS NULL
WHERE child.deleted_at IS NULL
AND COALESCE(child.pid, 0) <> 0
AND (
child.name = '通用'
OR child.code LIKE '%.default'
)
ORDER BY child.id;
```
判定原则:
- 这些记录多数是旧链路下的“默认子类型”兼容数据。
- 迁移时如果它们已经被真实二级业务类型替代,应考虑把规则集迁走后再停用或清理。
### 3.5 规则集重复挂载检查
确认同一个文档类型下,旧一级根和默认子级是否同时挂了相同规则集:
```sql
WITH target_groups AS (
SELECT
g.id,
g.document_type_id,
g.name,
g.code,
CASE
WHEN COALESCE(g.pid, 0) = 0 THEN 'old_root'
WHEN g.name = '通用' OR g.code LIKE '%.default' THEN 'default_child'
ELSE 'other_child'
END AS group_kind
FROM leaudit_evaluation_point_groups g
WHERE g.deleted_at IS NULL
AND g.document_type_id IS NOT NULL
)
SELECT
tg.document_type_id,
rgb.rule_set_id,
COUNT(*) AS binding_count,
STRING_AGG(tg.group_kind || ':' || tg.name, ' / ' ORDER BY tg.id) AS group_sources
FROM leaudit_rule_group_bindings rgb
JOIN target_groups tg
ON tg.id = rgb.group_id
WHERE rgb.deleted_at IS NULL
GROUP BY tg.document_type_id, rgb.rule_set_id
HAVING COUNT(*) > 1
ORDER BY tg.document_type_id, rgb.rule_set_id;
```
判定原则:
- 如果结果不为空,迁移脚本执行前要先确认是否允许去重。
- 否则迁移后可能出现:
- 同一规则集被重复搬到新二级分组
- 或者旧运行汇总结果不稳定
### 3.6 文档类型与入口模块映射检查
确认 `document_types` 是否都有稳定入口归属:
```sql
SELECT
dt.id,
dt.code,
dt.name,
dt.entry_module_id,
em.name AS entry_module_name
FROM leaudit_document_types dt
LEFT JOIN leaudit_entry_modules em
ON em.id = dt.entry_module_id
WHERE dt.deleted_at IS NULL
ORDER BY dt.id;
```
重点看:
- 是否存在 `entry_module_id IS NULL`
- 是否存在入口模块已被删除、停用或名称异常
- 是否存在“明明是合同类型却挂在卷宗入口”这类错误归属
### 3.7 规则集可运行性检查
迁移前需要确认二级分组绑定过去的规则集不是空壳:
```sql
SELECT
rs.id,
rs.rule_name,
rs.rule_type,
rs.current_version_id,
rs.fallback_version_id
FROM leaudit_rule_sets rs
WHERE rs.deleted_at IS NULL
ORDER BY rs.id;
```
这一步建议再配合业务接口实际看:
- 是否有可用版本
## 4. 正式迁移推荐执行顺序
### 步骤 1:先备份
必须先备份这几张表:
- `leaudit_evaluation_point_groups`
- `leaudit_rule_group_bindings`
- `leaudit_rule_type_bindings`
- `leaudit_document_types`
### 步骤 2:先补字段,不迁数据
先补结构层缺口:
1.`leaudit_evaluation_point_groups` 增加 `entry_module_id`
2. 建索引
3. 给已有一级业务大类根补入口绑定
建议值:
- `root.contract -> entry_module_id = 1`
- `root.casefile -> entry_module_id = 2`
### 步骤 3:重新跑巡检
字段补完后,重新执行:
- `scripts/precheck_rule_group_migration.sql`
确认:
- 一级业务大类根能正确看到入口模块字段
- 巡检结果仍无重复承接、重复绑定、空壳规则集等问题
### 步骤 4:测试库执行正式迁移
再执行:
- `scripts/migrate_rule_groups_to_business_roots.sql`
说明:
- 旧脚本 `scripts/migrate_rule_groups_to_doc_type_roots.sql` 仅保留兼容历史引用
- 不建议继续作为正式迁移脚本使用
### 步骤 5:迁后验证 4 个页面
必须验证:
1. `/rule-groups`
2. `/files/upload`
3. `/document-types/new?id=...`
4. `/documents/list`
## 5. 迁移脚本调整口径
基于当前巡检结果,正式脚本建议继续遵守:
1. 不要再硬编码“只有合同和卷宗”
2. 优先复用已存在业务大类根,不重复新造
3. 一级根编码必须统一,例如继续沿用:
- `root.contract`
- `root.casefile`
- 可用规则数是否大于 0
否则迁完结构后,上传仍然会失败,只是失败原因从“结构问题”变成“规则集不可运行”。
## 4. 执行前必须人工确认的业务映射
SQL 只能粗分,业务归属必须人工确认:
### 4.1 一级业务大类清单
至少要先列清:
- 哪些文档类型归到 `合同`
- 哪些文档类型归到 `卷宗`
- 是否已经存在第 3 个业务大类,如 `内部公文`
### 4.2 二级业务类型命名规范
要确认:
- 二级显示名称是否直接用 `document_types.name`
- 还是需要改成更贴近业务的名称
- 例如:`处罚-一般程序`
- 而不是数据库里原始的技术名
### 4.3 默认子类型保留策略
需要人工确认:
- 哪些文档类型仍保留 `默认子类型(通用)`
- 哪些必须拆成多个真实二级业务类型
## 5. 执行顺序建议
推荐按下面顺序推进:
1. 跑本清单中的查询 SQL,导出当前状态。
2. 形成一份 `document_type -> 一级业务大类 -> 目标二级名称` 的确认表。
3. 在测试库执行迁移脚本。
4. 验证以下页面:
- `/rule-groups`
- `/files/upload`
- `/document-types/new?id=...`
- `/documents/list`
5. 验证上传链路:
- 能否正确选择子类型
- 上传后是否命中预期规则集
- 合同附件追加链路是否仍正常
6. 测试通过后,再评估正式库执行窗口。
## 6. 当前不建议直接执行的情况
只要出现以下任一情况,都不建议直接执行迁移脚本:
- `document_types.entry_module_id` 还有大量空值
- 同一文档类型被多个旧组重复承接,但业务还没定论
- 规则集存在大量“可用规则数 = 0”
- 一级业务大类并不只有 `合同 / 卷宗`,但映射方案还没补
- 业务方还没确认二级命名与保留策略
## 7. 当前结论
截至当前,这套系统已经具备:
- 前端按新链路展示和解释的能力
- 后端兼容新旧结构的能力
- 新配置回写旧运行绑定表的能力
但数据库正式迁移这一步,仍然必须先做“数据盘点 + 业务确认 + 测试库演练”三件事,不能直接在正式库硬切。