# 内部公文模块现状偏差与对接补齐计划

## 1. 文档目的

本文档基于当前仓库代码、已有迁移设计文档，以及已确认的业务口径，回答 3 个问题：

- 当前 `govdoc` 模块业务上到底应该怎么跑
- 当前代码实际上跑成了什么状态
- 后续应该按什么顺序补齐，才能低风险落地

这份文档是后续前后端对接、数据库补齐、权限修复、页面改造的执行基线。

---

## 2. 已确认的业务结论

结合现有页面语义、引擎能力和你已确认的口径，当前正式业务结论如下。

### 2.1 模块定位

`govdoc` 不是公文流转系统，也不是审批系统。

它的第一阶段定位是：

- **内部公文格式审查模块**

核心能力是：

- 上传公文
- 自动发起格式审查
- 输出问题、规则结果、结构结果、报告产物
- 保留历史审查记录

### 2.2 业务主对象

当前应正式拍板：

- **主对象是文档**
- `run` 只是某份文档的一次审查执行记录

原因：

- 列表页已经确定是文档列表
- 删除、权限、原文下载天然属于文档
- 首页统计按文档最新结果算
- 用户认知对象是“这份公文”，不是“某次 run”

### 2.3 页面入口结论

详情页建议固定为：

- **按 `documentId` 进入详情**

详情页默认行为：

- 默认展示该文档最新一次审查结果
- 页面内可切换历史 run

因此正确语义是：

- 列表展示文档
- 详情入口指向文档
- 结果展示某个 run

### 2.4 审查流程结论

当前业务流程应为：

1. 用户上传公文
2. 创建平台文档主档，标记 `engine_type='govdoc'`
3. 自动创建一条新的 `run`
4. 投递异步任务
5. Worker 执行格式审查
6. 产出规则结果、结构结果、报告产物
7. 详情页默认展示最新 run，保留历史 run

### 2.5 权限与可见范围结论

- `common` 只能看自己上传的文档及结果
- `admin` 只能看本地区
- 报告、原文、findings、history 都必须继承文档可见性
- 删除是软删除
- 普通用户默认不开放文档更新/删除
- 需要保留重跑能力，但前端暂不开放按钮

### 2.6 指标与结果语义结论

- 分数是**公文格式规范符合度**
- 不是内容质量分
- 首页统计要按**每份文档的最新 run** 计算
- 不是把所有 run 累加

---

## 3. 当前实现的总体判断

结论先说：

- 当前仓库已经把 `govdoc` 的目录、接口骨架、前端页面壳、SQL 草案、Worker 编排骨架都铺出来了
- 但它还没有形成一个真正闭环的“可用模块”

当前状态更准确地说是：

- **已经完成模块迁移骨架**
- **但业务主模型、接口契约、运行链路、权限落地还没有完全收口**

这不是一个“修几个接口”的问题，而是一个“已经搭了半套系统，但业务模型还没完全统一”的问题。

---

## 4. 现状偏差分析

以下按 4 层来看：业务模型、前后端契约、运行链路、权限与统计。

### 4.1 业务模型偏差

#### 偏差 1：后端设计想走文档模型，前端详情仍然是 run 模型

后端控制器已经提供：

- `/govdoc/documents`
- `/govdoc/documents/{documentId}`
- `/govdoc/runs`
- `/govdoc/runs/{runId}/...`

说明后端设计方向其实已经倾向：

- 文档是主对象
- run 是子对象

但前端详情页实际还是：

- `/govdoc/[id]`

且页面直接把 `id` 当作 `auditId` 传给旧组件。

这意味着当前前端仍在延续旧的“run 即详情页主键”思路。

直接影响：

- 文档列表跳详情时主键语义不稳定
- 同一文档多次运行后，入口对象变成“当前 run”
- 后续历史 run 展示很难自然补进去

#### 偏差 2：列表页名义上是文档列表，数据上仍在混用文档 ID 和 run ID

当前前端列表适配时把：

- `audit_id = currentRunId ?? documentId`

这其实是在用一个字段混装两种对象：

- 有最新 run 时，这个 ID 代表 run
- 没有 run 时，这个 ID 代表 document

这会直接带来：

- 详情页路由语义漂移
- 删除、下载原文、查看结果时对象类型不确定
- 前端组件后续越来越难维护

#### 偏差 3：上传与发起审查的业务闭环还没有真正做完

业务上已经确认：

- 上传后应自动发起审查

但当前后端 `UploadDocument()` 只是占位返回；
`CreateRun()` 也是占位返回；
前端 `uploadAudit()` 虽然按“先上传、再建 run”的思路写了，但后端并没有真正完成这两个动作。

因此现在只是：

- 前端以为自己在走业务闭环
- 后端实际上还没有落地

### 4.2 前后端接口与路由契约偏差

#### 偏差 4：前端代理路径与实际代理实现不一致

当前前端 API 客户端里大量请求走的是：

- `/api/govdoc/...`

但仓库里实际存在的代理路由是：

- `/api/govdoc-audit/[...path]`

这代表至少在当前仓库状态下：

- 代码意图已经切到新模块路径
- 代理实现仍停留在旧路径

运行时风险很直接：

- 前端请求可能 404
- 或必须依赖外部额外 rewrite 才能工作

#### 偏差 5：前端页面路由已经叫 `govdoc`，但菜单和别名体系仍残留大量 `govdoc-audit`

当前仓库同时存在两套路由语义：

- 新路由：`/govdoc`
- 旧路由：`/govdoc-audit`

遗留点包括：

- 菜单映射
- 最小权限白名单
- breadcrumb
- 角色权限页面提示文案
- route alias 配置
- 某些入口页跳转目标

这意味着当前前端不是“已经迁完”，而是处于：

- 新旧路径并存
- 新路径只接了一半

直接后果：

- 菜单权限和真实页面可能不一致
- 用户访问入口与页面跳转链路可能绕回旧路径
- 后续排障会出现“接口没问题但入口不对”的假象

#### 偏差 6：SQL 菜单种子与前端真实详情路由不一致

当前 SQL 种子注册的是：

- `/govdoc/detail`

但前端真实详情页现在是：

- `/govdoc/[id]`

而业务推荐最终应为：

- `/govdoc/detail/:documentId`

这说明目前菜单/RBAC 路由、Next 页面路由、业务路由设计三者还没有统一。

### 4.3 后端服务实现偏差

#### 偏差 7：`GovdocServiceImpl` 大部分接口仍是占位实现

当前真正有一定查询逻辑的主要只有：

- `GetRunResult()`

其余关键接口基本仍是骨架：

- `UploadDocument()`
- `ListDocuments()`
- `GetDocumentDetail()`
- `UpdateDocument()`
- `DeleteDocument()`
- `CreateRun()`
- `GetRunStatus()`
- `GetRunFindings()`
- `GetRunEntities()`
- `GetRunParagraphs()`
- `GetRunStructure()`
- `GetRunOutline()`
- `GetReportHtml()`
- `GetReportDocx()`
- `DownloadOriginal()`

这意味着：

- 控制器接口已经暴露出来
- 但绝大部分业务服务还没有真正接平台主档、结果表、OSS、权限范围

#### 偏差 8：`GetRunResult()` 所依赖字段与当前建表 SQL 不一致

代码查询了：

- `govdoc_runs.rules_path`
- `govdoc_rule_results.skip_reason`

但当前建表 SQL 中并没有这两个字段。

这属于很典型的：

- 服务代码和 DDL 没同步

直接后果：

- 查询会报 SQL 字段不存在
- 或部署后不同环境表现不一致

#### 偏差 9：结果接口仍是 run 视角，缺少“文档详情聚合接口”

业务上真正需要的详情能力是：

- 输入 `documentId`
- 返回文档基础信息
- 返回最新 run 摘要
- 返回历史 run 列表
- 默认选中最新 run 的 findings / structure / outline / artifacts

当前后端虽然有：

- `GetDocumentDetail(documentId)`

但只是占位返回 `{"documentId": ...}`。

这意味着真正业务需要的“文档详情聚合接口”还没做。

### 4.4 Worker / Bridge / 运行时偏差

#### 偏差 10：Worker 调用 `GovdocRunner.Execute()` 时缺少必填 `RulesPath`

`GovdocRunner.Execute()` 的签名要求：

- `RulesPath: str`

但 Celery 任务调用时没有传这个参数。

这意味着当前一旦任务真正执行到这里，运行时就会直接报错。

#### 偏差 11：状态更新参数名不一致，`phase` 很可能写不进去

`StorageAdapter.UpdateRunStatus()` 定义的是：

- `Phase`

但调用时传的是：

- `phase=...`

由于这里大小写不一致，`Phase` 不会接收到值。

直接后果：

- `govdoc_runs.phase` 可能一直不更新
- 前端状态页看到的阶段信息不可信

#### 偏差 12：结果持久化只写了规则结果和产物，没有把文档维度聚合真正打通

当前 Bridge 更像是在做：

- run 生命周期管理
- 规则结果入库
- 报告产物索引入库

但还没有完全补齐：

- 文档最新 run 的选择逻辑
- 文档级状态汇总
- 文档级最新分数/最新结果统计口径
- 首页统计查询口径

所以底层虽然开始有 run 表，但“平台层文档视角”还没有真正建立。

### 4.5 权限、可见范围、删除语义偏差

#### 偏差 13：控制器做了登录校验，但还没真正落细粒度数据范围

当前控制器统一用了：

- `verify_access_token`

说明登录态有了。

但真正业务要求的是：

- `common` 仅本人
- `admin` 仅本地区
- 报告/原文/结果/历史全部继承文档范围

而这些逻辑目前还没有在 `GovdocServiceImpl` 中真正实现。

所以当前状态更接近：

- 只有“是否登录”
- 还没有“能看谁的数据”

#### 偏差 14：删除语义业务上要求软删除，但当前服务只是占位返回

业务上已经确认删除必须是：

- 软删除

当前 `DeleteDocument()` 只是返回：

- `{"documentId": ..., "deleted": True}`

没有真正处理：

- 文档主档软删
- 文件记录是否保留
- run 是否跟随隐藏
- 报告产物是否继续留存但不可见

#### 偏差 15：普通用户不应默认拥有更新/删除，但当前接口已开放，权限键还没真正落表

接口层已经有：

- `PATCH /documents/{documentId}`
- `DELETE /documents/{documentId}`

这本身没错，因为管理员要用。

但当前问题是：

- 角色默认能力
- 权限键
- 按角色/地区/本人范围控制

这些还没真正打通。

所以现在属于“接口先开出来了，但权限收口还没跟上”。

---

## 5. 当前代码实际反映出的真实业务状态

从代码现状反推，当前模块实际上处于下面这个阶段：

### 5.1 已经确定的部分

- 决定把 `govdoc-audit` 收口进主平台
- 决定复用主平台账号、权限、文档主档、OSS、Celery
- 决定新建 `govdoc_runs / govdoc_rule_results / govdoc_report_artifacts`
- 决定后端 API 挂到 `/govdoc`
- 决定前端页面也逐步迁到 `/govdoc`

这部分方向是对的。

### 5.2 还没有完全拍平的部分

- 详情页到底按 `documentId` 还是 `runId`
- 列表项到底代表文档还是运行
- 前端入口到底以 `govdoc` 还是 `govdoc-audit` 为准
- 路由种子、前端页面、菜单权限是否统一
- 文档级详情聚合接口长什么样
- 统计到底按 run 还是按文档最新结果

### 5.3 因此当前模块不是“坏了”，而是“未收口”

更准确地说：

- 架构方向基本正确
- 但业务主模型尚未完全统一
- 结果导致前端、后端、SQL、任务链各自朝着相近但不完全一致的方向在写

这就是当前所有错位问题的根因。

---

## 6. 建议的正式收口方案

为了后面少返工，建议现在先正式拍板以下模型，不再来回摇摆。

### 6.1 正式主模型

- 主对象：`document`
- 执行对象：`run`
- 结果对象：`rule_result`
- 产物对象：`artifact`

### 6.2 正式页面路由

- `/govdoc`
- `/govdoc/home`
- `/govdoc/upload`
- `/govdoc/audits` 或 `/govdoc/list`
- `/govdoc/detail/[documentId]`
- `/govdoc/rules`

建议最终二选一：

- 要么统一保留 `/govdoc/audits`
- 要么统一收敛为 `/govdoc/list`

不要两个同时长期并存。

从当前业务文案看，我更建议统一为：

- `/govdoc/list`
- `/govdoc/detail/[documentId]`

因为它更贴近“文档列表 / 文档详情”的业务语义。

### 6.3 正式接口模型

建议稳定成两层接口。

第一层：文档接口

- `POST /govdoc/documents`
- `GET /govdoc/documents`
- `GET /govdoc/documents/{documentId}`
- `PATCH /govdoc/documents/{documentId}`
- `DELETE /govdoc/documents/{documentId}`
- `GET /govdoc/documents/{documentId}/original`
- `GET /govdoc/documents/{documentId}/runs`

第二层：run 结果接口

- `POST /govdoc/runs`
- `GET /govdoc/runs/{runId}`
- `GET /govdoc/runs/{runId}/result`
- `GET /govdoc/runs/{runId}/findings`
- `GET /govdoc/runs/{runId}/entities`
- `GET /govdoc/runs/{runId}/structure`
- `GET /govdoc/runs/{runId}/outline`
- `GET /govdoc/runs/{runId}/paragraphs`
- `GET /govdoc/runs/{runId}/report/html`
- `GET /govdoc/runs/{runId}/report/docx`

其中：

- 页面入口以 `documentId` 为准
- 结果读取以 `runId` 为准

### 6.4 正式详情页返回模型

建议把 `GET /govdoc/documents/{documentId}` 做成真正的聚合详情：

- 文档基本信息
- 当前状态
- 当前最新 run 摘要
- 历史 run 列表
- 当前默认 runId
- 当前可用报告产物摘要

页面进入后：

- 先拿文档聚合详情
- 再按默认 runId 拉 findings / structure / outline / entities

这样前端会非常稳定。

---

## 7. 分阶段补齐计划

以下是建议执行顺序。顺序不能乱，因为前一层不收口，后一层会反复返工。

### Phase 1：先把业务主模型收口

目标：

- 明确 `document` 是主对象
- 统一详情入口为 `documentId`
- 统一列表项表达的是文档

具体动作：

- 前端详情路由改为 `/govdoc/detail/[documentId]`
- 列表接口返回文档维度 DTO，不再混用 `documentId/runId`
- 列表项保留 `latestRunId` 字段，但不能拿它充当主键
- SQL 路由种子、前端菜单、页面跳转统一成同一套路由

产出结果：

- 页面导航语义稳定
- 历史 run 可自然挂入详情页
- 后续删除、下载、权限继承都有明确主语

### Phase 2：补齐后端文档主链路

目标：

- 真正打通“上传文档 -> 创建主档 -> 标记为 govdoc -> 自动建 run”

具体动作：

- `UploadDocument()` 接入平台文档主档服务
- 文档主档写入 `engine_type='govdoc'`
- 上传后默认自动触发 `CreateRun()`
- `CreateRun()` 真正写入 `govdoc_runs`
- 投递 Celery 任务
- 回写 `leaudit_documents.processing_status/current_run_id`

产出结果：

- 用户上传后能真正看到一条 govdoc 文档
- 文档状态从上传到处理中可追踪

### Phase 3：修复 Worker 运行闭环

目标：

- 让异步任务真正可跑完

具体动作：

- 修复 `RulesPath` 来源与传参
- 修复 `UpdateRunStatus()` 的 `Phase/phase` 参数不一致
- 明确默认规则文件解析策略
- 补 run 开始时间、结束时间、失败信息
- 补结果持久化后的文档最新状态回写

产出结果：

- Celery worker 可以稳定完成一次 run
- 失败时数据库里有清晰可读的错误和状态

### Phase 4：补齐结果查询与详情聚合

目标：

- 让详情页真正能按业务方式展示

具体动作：

- `GetDocumentDetail()` 改为聚合查询
- 增加 `GET /documents/{documentId}/runs`
- `GetRunFindings()` / `GetRunEntities()` / `GetRunStructure()` / `GetRunOutline()` / `GetReportHtml()` / `GetReportDocx()` / `DownloadOriginal()` 全部接真实数据
- HTML/DOCX/original 下载统一继承文档权限

产出结果：

- 详情页默认展示最新结果
- 用户可切换历史 run
- 报告与原文下载链路完整

### Phase 5：补齐权限、数据范围、软删除

目标：

- 让模块真正符合平台权限模型

具体动作：

- 落 `govdoc:*:*` 权限键
- `common` 按本人过滤
- `admin` 按地区过滤
- `provincial_admin/super_admin` 按更大范围过滤
- 删除改为主档软删
- 结果/报告/历史查询全部继承文档范围

产出结果：

- 模块可以上线到真实账号体系
- 不会出现越权查看他人公文结果

### Phase 6：清理前端旧壳与统计口径

目标：

- 把遗留的 `govdoc-audit` 痕迹真正收口

具体动作：

- 统一 `/govdoc` 与 `/govdoc-audit` 路由
- 菜单、breadcrumb、minimal-scope、route alias 统一
- 首页统计改为按文档最新 run 聚合
- 前端隐藏重跑按钮，但保留后端能力

产出结果：

- 前端不再混用新旧模块名
- 用户看到的是完整一致的一套业务入口

---

## 8. 优先级建议

如果要按最小可用闭环来排，我建议优先级如下：

### P0：不修就跑不起来

- 统一详情主模型为 `documentId`
- 修复 `/api/govdoc` 与实际代理不一致
- 实现 `UploadDocument()` / `CreateRun()`
- 修复 `RulesPath` 缺失
- 修复 `phase` 状态写回失败
- 修复 DDL 与服务代码字段不一致

### P1：能跑但还不能交付

- 实现文档聚合详情
- 实现历史 run 查询
- 实现 findings / structure / outline / artifacts 真实读取
- 实现原文/HTML/DOCX 下载

### P2：上线前必须补齐

- 权限键
- 数据范围
- 软删除
- 首页统计口径
- 新旧路由与菜单彻底收口

---

## 9. 最终建议

当前最重要的不是马上补某一个接口，而是先统一下面这句话：

- **内部公文模块是“文档为主、run 为辅、详情按 documentId 进入、结果按 runId 展示”的业务模型。**

只要这个模型拍板，后面所有实现都可以顺下来：

- 列表怎么查
- 详情怎么开
- 历史怎么挂
- 统计怎么算
- 权限怎么继承
- 删除怎么定义

如果这句话不拍板，后面无论先修前端还是后端，都会反复返工。

---

## 10. 审核结论

基于当前业务确认和代码现状，我给出的正式判断是：

- 当前模块方向是对的
- 当前实现还未闭环
- 最大问题不是单点 bug，而是“主业务模型尚未完全统一”

建议正式以本文方案收口后，再进入代码补齐阶段。

这样改一次，后面才不会反复拆。
