14 KiB
老项目交叉评查逻辑梳理
目标:整理老项目
docauditai中“交叉评查”模块的真实业务链路、数据模型、权限边界和当前新项目承接情况,便于后续迁移、重构和对照实现。
1. 项目路径与主要参考文件
1.1 老项目路径
- 老项目根目录:
/home/wren-dev/Porject/docauditai
1.2 当前新项目前端路径
- 新前端目录:
/home/wren-dev/Porject/leaudit-platform/new_doc_review
1.3 本次整理的核心参考文件
- 老逻辑总说明:
/home/wren-dev/Porject/docauditai/dcos/cross_review_business_logic.md - 老表关系图:
/home/wren-dev/Porject/docauditai/dcos/cross_review_table_relationships.md - 老路由主文件:
/home/wren-dev/Porject/docauditai/app/routes/v2/crossreview/cross_review.py - 老服务主文件:
/home/wren-dev/Porject/docauditai/services/documents/v2/cross_review_service.py - 历史接口文档:
new_doc_review/auth_doc/交叉评查接口文档.md
2. 一句话定义
交叉评查不是重新跑 OCR、抽取、规则评查,而是围绕已经存在的评查结果做“多人协同复核”。
它的核心目标是:
- 把一批文档挂到一个交叉评查任务里
- 让任务参与人围绕具体评查点发起加分/减分提案
- 让参与人对提案投票
- 提案通过后,把分数回写到原始评查结果
- 由负责人手动确认文档评查完成,并最终推动任务完成
换句话说,这是一条“评后协作复核”流程,不是底层文档处理流水线。
3. 核心数据模型
交叉评查主要依赖以下几张表:
3.1 cross_examination_tasks
交叉评查任务主表。
核心字段:
assigner_id:任务创建者user_ids:参与评查的用户列表principal_user_ids:主要负责人列表task_status:任务状态,常见值有in_progress、completedtask_name:任务名称doc_type:任务绑定的文档类型编码task_type:任务类型,如CITY、DISTRICT
业务含义:
- 一个任务绑定一组参与人和一批文档
- 创建者和主要负责人拥有更高权限
3.2 cross_task_document_mapping
交叉评查任务与文档的映射表。
核心字段:
task_iddocument_idaudit_statusdeleted_at
业务含义:
- 一条记录表示某文档被纳入某个交叉评查任务
audit_status=1表示该文档在该任务内已确认完成- 这里的
audit_status是任务内状态,不等于documents.audit_status
3.3 cross_scoring_proposals
交叉评分提案表。
核心字段:
document_idevaluation_point_idevaluation_result_idproposer_idproposed_scorereasonstatusdeleted_at
业务含义:
- 针对某个评查结果提出加分或减分建议
- 常见状态:
pending、approved、rejected
3.4 cross_opinion_votes
交叉评查投票表。
核心字段:
proposal_idvoter_idvote_typedeleted_at
业务含义:
- 参与者对提案表态
- 支持
agree、disagree、cancel - 提案创建后,系统会自动给提案人补一票
agree
3.5 evaluation_results
旧评查结果表,是交叉评查当前的评分底座。
主要作用:
- 读取当前机器分 / 最终分 / 评查结果消息
- 在提案通过后更新
final_score
3.6 evaluation_points
评查点定义表。
交叉评查依赖它读取:
- 单点评查满分
score - 风险级别
fail_messagepost_actionsuggestion_message_type
4. 主业务链路
4.1 创建任务
核心服务:
assign_cross_examination_tasks()- 文件位置:
/home/wren-dev/Porject/docauditai/services/documents/v2/cross_review_service.py
处理逻辑:
- 把
assigner_id强制加入user_ids并去重 - 创建
cross_examination_tasks - 为每个文档写入
cross_task_document_mapping - 初始状态设置为
task_status = in_progress
补充说明:
- 在老项目里,任务创建很多时候不是单独走一个“先建任务”的管理入口
- 更常见的是在上传文档后直接触发任务分配
- 相关入口可见:
/home/wren-dev/Porject/docauditai/app/routes/v2/documents/documents.py
4.2 任务列表
接口:
POST /tasks/user_tasks
实现位置:
- 路由:
/home/wren-dev/Porject/docauditai/app/routes/v2/crossreview/cross_review.py
逻辑特点:
- 按
current_user.id = ANY(user_ids)查询用户参与的任务 - 进度不是看
documents.audit_status - 而是看
cross_task_document_mapping.audit_status
返回内容通常包括:
- 任务 ID
- 任务名称
- 任务状态
- 文档类型
- 任务类型
- 创建时间
- 进度百分比
- 总文档数
- 参与地区
4.3 任务下文档列表
老项目实际上有两套接口思路。
旧列表接口
POST /tasks/{task_id}/documents
特点:
- 返回任务下文档的分页列表
- 聚合每个文档的评查统计、问题摘要、得分等信息
新版版本归纳接口
GET /tasks/{task_id}/documents
对应服务:
get_task_documents_with_versions()- 文件:
/home/wren-dev/Porject/docauditai/services/documents/v2/cross_review_service.py
版本归纳规则:
- 同一任务内
(name, type_id)相同的文档归为一组 - 组内按
created_at降序排列 - 最新上传的是当前版本
- 其他的是历史版本
统计信息来源:
- 文档实体:
documents - 评查统计:
evaluation_results + evaluation_points - 得分规则:优先
final_score,否则按机器结果/通过结果推导
4.4 发起评分提案
接口:
POST /proposals
路由位置:
/home/wren-dev/Porject/docauditai/app/routes/v2/crossreview/cross_review.py
服务位置:
create_scoring_proposal()/home/wren-dev/Porject/docauditai/services/documents/v2/cross_review_service.py
核心校验规则:
- 用户必须有文档访问权限
evaluation_result_id必填evaluation_result_id必须真实属于当前document_id- 文档必须已纳入某个交叉评查任务
- 提案人必须是该任务参与者
- 同一用户不能对同一文档、同一评查点重复创建未删除提案
- 不允许创建 0 分提案
- 当前分数已经为 0 时,不允许继续扣分
- 当前分数已经达到该评查点满分时,不允许继续加分
创建成功后:
- 插入
cross_scoring_proposals - 自动给提案人插入一条
agree投票 - 立即触发提案状态检查
4.5 投票
接口:
POST /proposals/{proposal_id}/votes
服务:
create_opinion_vote()
支持动作:
agreedisagreecancel
主要规则:
- 只有任务参与者能投票
- 已经
approved或rejected的提案不允许再投/撤销 cancel本质是把投票软删除
4.6 提案状态判定
内部方法:
_check_and_process_proposal_status()
判定规则:
- 找到提案关联文档
- 取该文档所在的最新任务
- 获取任务参与人数
n - 统计同意票
a与反对票d - 通过阈值为
floor(n / 2) + 1
状态流转:
agree >= threshold->approveddisagree >= threshold->rejected- 即使剩余未投票者全部同意也无法达到阈值 ->
rejected - 否则保持
pending
这说明老逻辑走的是“绝对多数制”,不是全票制。
4.7 提案通过后的分数回写
内部方法:
_update_evaluation_result_score()
处理逻辑:
- 找到提案关联的
evaluation_result_id - 读取当前
evaluation_results.final_score / machine_score - 读取评查点满分
evaluation_points.score - 将
proposed_score累加到最终分数 - 做边界保护:
- 最低不小于 0
- 最高不超过评查点满分
结论:
- 老交叉评查的“最终结果”是直接回写旧评查结果表
evaluation_results.final_score
4.8 查看文档上的所有提案
接口:
POST /proposals/document
服务:
get_proposals_by_document()
典型返回字段:
- 提案 ID
- 评查点名称
- 建议分值
- 理由
- 提案人
- 已投票列表
- 同意人
- 反对人
- 待投票人
- 当前用户是否可投票
- 发现问题文案
- 提案状态
这是详情页“交叉意见区”的核心数据接口。
4.9 获取待处理提案列表
接口:
POST /proposals/details
服务:
get_proposals_with_details()
作用:
- 查当前用户需要处理的待投票提案
过滤规则:
- 只看
pending - 排除自己发起的提案
- 只看自己参与任务下的文档
4.10 检查是否存在未投票用户
接口:
POST /proposals/document/check_pending_votes
服务:
check_pending_votes_by_document()
权限要求:
- 只有任务创建者或主要负责人可以调用
作用:
- 在确认完成前检查该文档下是否还有提案未完成投票
4.11 确认文档评查完成
接口:
POST /tasks/{task_id}/documents/{document_id}/complete
服务:
complete_document_audit()
主要规则:
- 只有
assigner_id或principal_user_ids中的用户才有权限确认 - 确认后会把
cross_task_document_mapping.audit_status改为1 - 如果任务下所有文档都完成,则把
cross_examination_tasks.task_status改成completed
注意:
- 文档是否完成是“任务内状态”
- 任务是否完成是所有任务内文档完成后的汇总状态
4.12 检查当前用户是否可确认完成
接口:
GET /tasks/{task_id}/can-confirm
服务:
check_confirm_permission()
作用:
- 前端先调用此接口,再决定是否显示“确认完成”按钮
4.13 向任务补文档 / 补附件
相关接口:
POST /tasks/{task_id}/documents/{document_id}/append_attachmentsPOST /tasks/{task_id}/upload_documents
核心规则:
- 只有任务创建者或主要负责人可以操作
- 追加附件会创建新文档,而不是覆盖老文档
- 新文档会重新写入
cross_task_document_mapping - 新文档默认
audit_status = 0 - 同名同类型文档会自动并入版本链
5. 权限模型
交叉评查存在三层权限。
5.1 文档访问权限
发起提案和查看文档提案前,会先检查用户是否有权访问该文档。
5.2 任务参与权限
任务参与者来自 cross_examination_tasks.user_ids。
参与者可以:
- 发起提案
- 对提案投票
- 查看自己参与任务下的任务、文档、提案和进度
5.3 高权限用户
高权限用户包括:
- 任务创建者
assigner_id - 主要负责人
principal_user_ids
高权限用户可以:
- 确认文档评查完成
- 检查文档下是否仍有待投票人员
- 为任务文档追加附件
- 向任务继续上传新文档
6. 交叉评查与 PostgREST 权限扩展
老项目还有一个关键点:交叉评查不是只靠业务接口做权限控制,代理层也会扩展文档可见范围。
关键文件:
/home/wren-dev/Porject/docauditai/app/routes/postgrest.py
它的作用是:
- 获取用户参与的交叉评查任务
- 提取这些任务关联的文档 ID
- 把这些文档加入用户可访问范围
这意味着:
- 用户即使不在原始文档所属部门
- 只要参与了交叉评查任务,也可能被允许跨部门访问这些任务文档
这部分是老系统交叉评查跨部门协同的重要权限补丁。
7. 几个必须记住的业务事实
- 交叉评查不是重新跑评查,而是消费旧
evaluation_results - 提案通过后会直接回写
evaluation_results.final_score - 文档完成状态落在
cross_task_document_mapping.audit_status - 任务完成状态落在
cross_examination_tasks.task_status - 文档完成不是投票自动完成,而是负责人手动确认
- 同名同类型文档在任务内会被视为一条版本链
8. 当前新项目承接情况
当前新项目前端已经有完整交叉评查页面,但其业务底座仍然大量依赖老接口和老表思路。
8.1 新前端页面
new_doc_review/app/routes/cross-checking._index.tsxnew_doc_review/app/routes/cross-checking.result.tsxnew_doc_review/app/routes/cross-checking.upload.tsx
8.2 新前端接口封装
new_doc_review/app/api/cross-checking/cross-files.tsnew_doc_review/app/api/cross-checking/cross-file-result.tsnew_doc_review/app/api/cross-checking/cross-files-upload.ts
8.3 仍在使用的老接口口径
前端仍直接调用这些接口:
/api/v2/cross_review/.../admin/v2/cross_review/.../admin/v2/documents/cross_review/documents/upload_and_assign/admin/cross_review/tasks/assign
8.4 当前观察结论
- 新前端页面壳子已经迁到
new_doc_review - 但交叉评查业务底座仍大体延续老项目
docauditai的接口和数据模型 - 在
fastapi_modules/fastapi_leaudit中,没有检索到一套完整的新交叉评查业务路由/服务闭环 - 只看到少量兼容读取老表的痕迹,例如
cross_scoring_proposals
9. 后续迁移时最难拆的三个耦合点
如果后续要把交叉评查彻底迁到新平台,最难拆的不是页面,而是以下 3 个耦合点:
9.1 提案通过后回写旧评查结果
cross_scoring_proposals->evaluation_results.final_score
9.2 任务内文档完成状态
cross_task_document_mapping.audit_status决定任务内文档是否完成
9.3 跨任务的文档访问权限扩展
app/routes/postgrest.py对交叉任务文档做了额外放行
10. 迁移判断
从当前代码和前端调用情况看,可以做出一个比较明确的判断:
- 现在的交叉评查“前端承载是新的”
- 但“业务模型、接口语义、分数回写、权限扩展”仍然主要是老系统延续下来的
因此,后续如果要迁移:
- 不能只迁页面
- 也不能只补几个接口
- 必须连同任务模型、提案模型、投票模型、评分回写模型、跨部门访问权限一起迁
11. 推荐继续查看的文件
如果后续要继续深挖,建议按这个顺序看:
/home/wren-dev/Porject/docauditai/dcos/cross_review_business_logic.md/home/wren-dev/Porject/docauditai/services/documents/v2/cross_review_service.py/home/wren-dev/Porject/docauditai/app/routes/v2/crossreview/cross_review.pynew_doc_review/app/api/cross-checking/cross-files.tsnew_doc_review/app/api/cross-checking/cross-file-result.ts
整理时间:2026-05-07