Files
leaudit-platform-frontend/auth_doc/交叉评查新增功能设计(2).md
LiangShiyong 1658bb1c6f feat: 1. 重构交叉评查任务的文档列表的显示,对接接口查询当前任务的文档相关信息。
2.文档上传通过接口去查询是否存在同名的文件,做上传前拦截提示。
3.交叉评查的评查结果也同步添加企查查的企业信息查询模块。
4. 封装上传附件和上传模板的模态框的组件,在交叉评查的文档列表中引入显示。
5. 交叉评查的评查结果中关于合同类型的文档同步加入结构比对的功能。
2025-12-13 07:18:37 +08:00

43 KiB
Raw Permalink Blame History

交叉评查模块 - 新增功能设计文档

本文档从《交叉评查接口文档.md》中提取关键接口设计,供开发参考。

开发状态

功能 状态 完成日期
任务文档列表(含版本归纳) 已完成 2025-12-13
追加附件 已完成 2025-12-13
模板对比功能接口组 已完成 2025-12-13

数据库变更cross_examination_tasks 表新增 template_comparison_id 字段

相关文件

  • 路由层:app/routes/v2/crossreview/cross_review.py
  • 服务层:services/documents/v2/cross_review_service.py
  • Celery任务:services/documents/tasks.py

9. 获取任务下文档列表(支持版本归纳)

GET /api/v2/cross_review/tasks/{task_id}/documents

获取指定任务下的文档列表,支持版本归纳功能。同一任务内同名且同类型的文档会被归纳为版本组,最新上传的为当前版本,其余为历史版本。仅任务参与者可访问

数据结构对齐: 返回格式与 GET /admin/versions/documents-list 保持一致,便于前端复用组件。

权限: cross_review:task:read

路径参数

参数 类型 说明
task_id int 任务ID(必须大于0

请求参数

字段 类型 必填 默认值 说明
page int 1 页码(从1开始)
page_size int 10 每页数量(最大100
keyword string null 模糊搜索关键字(匹配文件名称或文档编号)

筛选逻辑说明

keyword 模糊搜索:

  • 使用 ILIKE 模糊匹配,不区分大小写
  • 同时匹配两个字段(OR 关系):
    • documents.name - 文件名称
    • documents.document_number - 文档编号/文书号
  • 筛选作用于当前版本,如果当前版本匹配则显示该版本组(包含所有历史版本)

筛选与版本的关系

场景:任务下有2个版本组
├── 车辆维保合同.docx (v2, v1)
└── 设备维保合同.docx (v1)

用户搜索 keyword="车辆"
└── 只返回"车辆维保合同.docx"这一组,包含v2和v1两个版本

请求示例

示例1:获取全部文档(分页)

{
  "page": 1,
  "page_size": 10
}

示例2:模糊搜索

{
  "page": 1,
  "page_size": 10,
  "keyword": "维保合同"
}

版本归纳逻辑

核心规则

  1. 分组条件: 按 (name, type_id) 分组(同名且同类型的文档归为一组)
  2. 版本排序: 同组内按 created_at 降序排列
  3. 当前版本: 每组中最新上传的文档为当前版本
  4. 历史版本: 每组中其余文档为历史版本
  5. 版本号计算: 按 created_at 升序自动编号(1, 2, 3...),最新的版本号最大

示例场景

任务 208 关联的文档(cross_task_document_mapping:
├── document_id=2157, name="车辆维保合同范本.docx", type_id=1, created_at=2025-12-11
├── document_id=2200, name="车辆维保合同范本.docx", type_id=1, created_at=2025-12-13  ← 继续上传
└── document_id=2199, name="设备维保合同范本.docx", type_id=1, created_at=2025-12-13

按 (name, type_id) 分组后:
├── ("车辆维保合同范本.docx", 1) → 当前版本: 2200(v2), 历史版本: [2157(v1)]
└── ("设备维保合同范本.docx", 1) → 当前版本: 2199(v1), 历史版本: []

返回文档数: 2(按分组后的唯一文档计数)

业务逻辑

  1. 权限验证
    • 验证当前用户是否是任务的参与者(在 user_ids 中)
  2. 文档查询
    • cross_task_document_mapping 获取任务关联的所有文档ID
    • 关联 documents 表获取文档详情
    • 应用筛选条件(类型、日期、关键字)
  3. 版本分组
    • (name, type_id) 分组
    • 每组内按 created_at 降序排列
    • 第一个为当前版本,其余为历史版本
  4. 分页处理
    • 分页基于分组后的文档数(唯一文档),不是原始记录数
    • 例如:10个文档记录,分组后5个唯一文档,total=5
  5. 评查统计(批量计算)
    • 统计当前版本和所有历史版本的评查结果
    • 计算通过/警告/错误/人工审核数量
    • 获取问题消息列表
    • 计算最终得分和满分

响应示例

{
  "total": 2,
  "page": 1,
  "page_size": 10,
  "total_pages": 1,
  "documents": [
    {
      "id": 2200,
      "name": "车辆维保合同范本.docx",
      "path": "/documents/2025/12/2200.docx",
      "version_number": 2,
      "created_at": "2025-12-13T10:00:00Z",
      "status": "Processed",
      "file_size": 25018,
      "document_number": "HT-2024-001",
      "type_id": 1,
      "type_name": "合同",
      "upload_time": "2025-12-13T10:00:00Z",
      "audit_status": 0,

      "total_evaluation_points": 55,
      "pass_count": 51,
      "warning_count": 1,
      "error_count": 3,
      "manual_count": 2,
      "issue_count": 4,

      "warning_messages": ["日期格式不规范"],
      "error_messages": ["缺少当事人签名", "金额大写与小写不一致", "缺少合同编号"],
      "issue_messages": ["缺少当事人签名", "金额大写与小写不一致"],
      "manual_messages": ["骑缝章识别效果不佳,请人工核对", "签名真实性需人工确认"],

      "final_score": 85.5,
      "full_score": 100,
      "score_summary": "85.5/100",
      "score_percent": 85.5,

      "total_versions": 2,
      "history_versions": [
        {
          "id": 2157,
          "version_number": 1,
          "created_at": "2025-12-11T17:02:35Z",
          "upload_time": "2025-12-11T17:02:35Z",
          "file_size": 25018,
          "path": "/documents/2025/12/2157.docx",
          "status": "Failed",
          "document_number": null,
          "type_id": 1,
          "type_name": "合同",
          "audit_status": 0,
          "total_evaluation_points": 55,
          "pass_count": 40,
          "warning_count": 5,
          "error_count": 10,
          "manual_count": 0,
          "issue_count": 15,
          "warning_messages": ["日期格式不规范", "条款顺序混乱"],
          "error_messages": ["缺少甲方签名", "合同金额错误"],
          "issue_messages": ["缺少甲方签名", "合同金额错误"],
          "manual_messages": ["盖章位置需人工确认"],
          "final_score": 72.5,
          "full_score": 100,
          "score_summary": "72.5/100",
          "score_percent": 72.5
        }
      ]
    },
    {
      "id": 2199,
      "name": "设备维保合同范本.docx",
      "path": "/documents/2025/12/2199.docx",
      "version_number": 1,
      "created_at": "2025-12-13T09:30:00Z",
      "status": "Processed",
      "file_size": 26686,
      "document_number": "SB-2024-001",
      "type_id": 1,
      "type_name": "合同",
      "upload_time": "2025-12-13T09:30:00Z",
      "audit_status": 1,

      "total_evaluation_points": 55,
      "pass_count": 55,
      "warning_count": 0,
      "error_count": 0,
      "manual_count": 0,
      "issue_count": 0,

      "warning_messages": [],
      "error_messages": [],
      "issue_messages": [],
      "manual_messages": [],

      "final_score": 100,
      "full_score": 100,
      "score_summary": "100/100",
      "score_percent": 100,

      "total_versions": 1,
      "history_versions": []
    }
  ]
}

数据结构定义

TypeScript 类型定义
/**
 * 交叉评查任务文档列表响应
 */
interface CrossReviewDocumentListResponse {
  /** 总文档数(按版本分组后的唯一文档数) */
  total: number;
  /** 当前页码 */
  page: number;
  /** 每页数量 */
  page_size: number;
  /** 总页数 */
  total_pages: number;
  /** 文档列表 */
  documents: CrossReviewDocumentWithVersion[];
}

/**
 * 文档信息(含版本和评查统计)
 */
interface CrossReviewDocumentWithVersion {
  // ========== 基本信息 ==========
  /** 当前版本的文档ID */
  id: number;
  /** 文档名称 */
  name: string;
  /** 文件存储路径 */
  path: string;
  /** 当前版本号(最大值,从1开始) */
  version_number: number;
  /** 创建时间(ISO 8601格式) */
  created_at: string;  // "2025-12-13T10:00:00Z"
  /** 文档处理状态 */
  status: "Waiting" | "Cutting" | "Extractioning" | "Evaluationing" | "Failed" | "Processed";
  /** 文件大小(字节) */
  file_size: number;
  /** 文书号/文档编号(可为null) */
  document_number: string | null;
  /** 文档类型ID */
  type_id: number;
  /** 文档类型名称 */
  type_name: string;
  /** 上传时间(ISO 8601格式) */
  upload_time: string;

  // ========== 任务内评查状态 ==========
  /**
   * 任务内评查完成状态(来自 cross_task_document_mapping 表)
   *
   * ⚠️ 注意:此字段与 documents 表的 audit_status 含义不同!
   * - cross_task_document_mapping.audit_status: 0=未评查, 1=已评查(本接口使用)
   * - documents.audit_status: -2~2 的审核状态(普通文档列表使用)
   *
   * 0: 未评查(任务内该文档尚未完成评查)
   * 1: 已评查(任务内该文档已确认完成评查)
   */
  audit_status: 0 | 1;

  // ========== 评查统计 ==========
  /** 总评查点数 */
  total_evaluation_points: number;
  /** 通过的评查点数量 */
  pass_count: number;
  /** 警告的评查点数量(severity=warning或info */
  warning_count: number;
  /** 错误的评查点数量(severity=error */
  error_count: number;
  /** 需人工审核的评查点数量(post_action=manual */
  manual_count: number;
  /** 问题总数(warning_count + error_count */
  issue_count: number;

  // ========== 评查消息列表 ==========
  /** 警告消息列表 */
  warning_messages: string[];
  /** 错误消息列表 */
  error_messages: string[];
  /** 问题消息列表(综合:警告+错误) */
  issue_messages: string[];
  /** 需人工确认的消息列表 */
  manual_messages: string[];

  // ========== 交叉评查特有:分数信息 ==========
  /** 最终得分(经过提案投票调整后的分数) */
  final_score: number;
  /** 满分 */
  full_score: number;
  /** 得分摘要(如 "85.5/100" */
  score_summary: string;
  /** 得分百分比(0-100 */
  score_percent: number;

  // ========== 版本信息 ==========
  /** 总版本数 */
  total_versions: number;
  /** 历史版本列表(按created_at降序,不包含当前版本) */
  history_versions: HistoryVersion[];
}

/**
 * 历史版本信息
 *
 * 每个历史版本都有独立的:
 * - 评查统计(pass_count, warning_count, error_count, manual_count
 * - 评查消息列表(warning_messages, error_messages, issue_messages, manual_messages
 * - 分数信息(final_score, full_score, score_summary, score_percent
 * - 任务内评查状态(audit_status
 */
interface HistoryVersion {
  // ========== 基本信息 ==========
  /** 历史版本的文档ID */
  id: number;
  /** 版本号(从1开始,数字越小越旧) */
  version_number: number;
  /** 创建时间(ISO 8601格式) */
  created_at: string;
  /** 文件大小(字节) */
  file_size: number;
  /** 文件存储路径 */
  path: string;
  /** 文档处理状态 */
  status: "Waiting" | "Cutting" | "Extractioning" | "Evaluationing" | "Failed" | "Processed";
  /** 文书号/文档编号(可为null) */
  document_number: string | null;
  /** 文档类型ID */
  type_id: number;
  /** 文档类型名称 */
  type_name: string;
  /** 上传时间(ISO 8601格式) */
  upload_time: string;

  // ========== 任务内评查状态(每个版本独立) ==========
  /**
   * 任务内评查完成状态(来自 cross_task_document_mapping 表)
   * 0: 未评查
   * 1: 已评查
   */
  audit_status: 0 | 1;

  // ========== 评查统计(每个版本独立) ==========
  /** 总评查点数 */
  total_evaluation_points: number;
  /** 通过的评查点数量 */
  pass_count: number;
  /** 警告的评查点数量 */
  warning_count: number;
  /** 错误的评查点数量 */
  error_count: number;
  /** 需人工审核的评查点数量 */
  manual_count: number;
  /** 问题总数 */
  issue_count: number;

  // ========== 评查消息列表(每个版本独立) ==========
  /** 警告消息列表 */
  warning_messages: string[];
  /** 错误消息列表 */
  error_messages: string[];
  /** 问题消息列表(综合:警告+错误) */
  issue_messages: string[];
  /** 需人工确认的消息列表 */
  manual_messages: string[];

  // ========== 交叉评查特有:分数信息(每个版本独立) ==========
  /** 最终得分(经过提案投票调整后) */
  final_score: number;
  /** 满分 */
  full_score: number;
  /** 得分摘要(如 "85.5/100" */
  score_summary: string;
  /** 得分百分比(0-100 */
  score_percent: number;
}
JSON Schema 定义
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "CrossReviewDocumentListResponse",
  "description": "交叉评查任务文档列表响应",
  "type": "object",
  "required": ["total", "page", "page_size", "total_pages", "documents"],
  "properties": {
    "total": {
      "type": "integer",
      "description": "总文档数(按版本分组后的唯一文档数)",
      "minimum": 0
    },
    "page": {
      "type": "integer",
      "description": "当前页码",
      "minimum": 1
    },
    "page_size": {
      "type": "integer",
      "description": "每页数量",
      "minimum": 1,
      "maximum": 100
    },
    "total_pages": {
      "type": "integer",
      "description": "总页数",
      "minimum": 0
    },
    "documents": {
      "type": "array",
      "description": "文档列表",
      "items": { "$ref": "#/definitions/CrossReviewDocumentWithVersion" }
    }
  },
  "definitions": {
    "CrossReviewDocumentWithVersion": {
      "type": "object",
      "description": "文档信息(含版本和评查统计)",
      "required": [
        "id", "name", "path", "version_number", "created_at", "status",
        "file_size", "type_id", "type_name", "audit_status",
        "total_evaluation_points", "pass_count", "warning_count", "error_count",
        "manual_count", "issue_count", "warning_messages", "error_messages",
        "issue_messages", "manual_messages", "final_score", "full_score",
        "score_summary", "score_percent", "total_versions", "history_versions"
      ],
      "properties": {
        "id": { "type": "integer", "description": "当前版本的文档ID" },
        "name": { "type": "string", "description": "文档名称" },
        "path": { "type": "string", "description": "文件存储路径" },
        "version_number": { "type": "integer", "description": "当前版本号", "minimum": 1 },
        "created_at": { "type": "string", "format": "date-time", "description": "创建时间" },
        "status": {
          "type": "string",
          "enum": ["Waiting", "Cutting", "Extractioning", "Evaluationing", "Failed", "Processed"],
          "description": "文档处理状态"
        },
        "file_size": { "type": "integer", "description": "文件大小(字节)", "minimum": 0 },
        "document_number": { "type": ["string", "null"], "description": "文书号/文档编号" },
        "type_id": { "type": "integer", "description": "文档类型ID" },
        "type_name": { "type": "string", "description": "文档类型名称" },
        "upload_time": { "type": "string", "format": "date-time", "description": "上传时间" },
        "audit_status": {
          "type": "integer",
          "enum": [0, 1],
          "description": "任务内评查完成状态:0=未评查, 1=已评查(来自cross_task_document_mapping表)"
        },
        "total_evaluation_points": { "type": "integer", "description": "总评查点数", "minimum": 0 },
        "pass_count": { "type": "integer", "description": "通过的评查点数量", "minimum": 0 },
        "warning_count": { "type": "integer", "description": "警告的评查点数量", "minimum": 0 },
        "error_count": { "type": "integer", "description": "错误的评查点数量", "minimum": 0 },
        "manual_count": { "type": "integer", "description": "需人工审核的评查点数量", "minimum": 0 },
        "issue_count": { "type": "integer", "description": "问题总数", "minimum": 0 },
        "warning_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "警告消息列表"
        },
        "error_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "错误消息列表"
        },
        "issue_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "问题消息列表"
        },
        "manual_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "需人工确认的消息列表"
        },
        "final_score": { "type": "number", "description": "最终得分", "minimum": 0 },
        "full_score": { "type": "number", "description": "满分", "minimum": 0 },
        "score_summary": { "type": "string", "description": "得分摘要", "example": "85.5/100" },
        "score_percent": { "type": "number", "description": "得分百分比", "minimum": 0, "maximum": 100 },
        "total_versions": { "type": "integer", "description": "总版本数", "minimum": 1 },
        "history_versions": {
          "type": "array",
          "items": { "$ref": "#/definitions/HistoryVersion" },
          "description": "历史版本列表"
        }
      }
    },
    "HistoryVersion": {
      "type": "object",
      "description": "历史版本信息(每个版本独立的评查统计、消息列表、分数信息)",
      "required": [
        "id", "version_number", "created_at", "file_size", "path", "status",
        "type_id", "type_name", "upload_time", "audit_status",
        "total_evaluation_points", "pass_count", "warning_count", "error_count", "manual_count", "issue_count",
        "warning_messages", "error_messages", "issue_messages", "manual_messages",
        "final_score", "full_score", "score_summary", "score_percent"
      ],
      "properties": {
        "id": { "type": "integer", "description": "历史版本的文档ID" },
        "version_number": { "type": "integer", "description": "版本号(从1开始,数字越小越旧)", "minimum": 1 },
        "created_at": { "type": "string", "format": "date-time", "description": "创建时间" },
        "file_size": { "type": "integer", "description": "文件大小(字节)", "minimum": 0 },
        "path": { "type": "string", "description": "文件存储路径" },
        "status": {
          "type": "string",
          "enum": ["Waiting", "Cutting", "Extractioning", "Evaluationing", "Failed", "Processed"],
          "description": "文档处理状态"
        },
        "document_number": { "type": ["string", "null"], "description": "文书号/文档编号" },
        "type_id": { "type": "integer", "description": "文档类型ID" },
        "type_name": { "type": "string", "description": "文档类型名称" },
        "upload_time": { "type": "string", "format": "date-time", "description": "上传时间" },
        "audit_status": {
          "type": "integer",
          "enum": [0, 1],
          "description": "任务内评查完成状态:0=未评查, 1=已评查(来自cross_task_document_mapping表)"
        },
        "total_evaluation_points": { "type": "integer", "description": "总评查点数", "minimum": 0 },
        "pass_count": { "type": "integer", "description": "通过的评查点数量", "minimum": 0 },
        "warning_count": { "type": "integer", "description": "警告的评查点数量", "minimum": 0 },
        "error_count": { "type": "integer", "description": "错误的评查点数量", "minimum": 0 },
        "manual_count": { "type": "integer", "description": "需人工审核的评查点数量", "minimum": 0 },
        "issue_count": { "type": "integer", "description": "问题总数", "minimum": 0 },
        "warning_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "警告消息列表"
        },
        "error_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "错误消息列表"
        },
        "issue_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "问题消息列表"
        },
        "manual_messages": {
          "type": "array",
          "items": { "type": "string" },
          "description": "需人工确认的消息列表"
        },
        "final_score": { "type": "number", "description": "最终得分", "minimum": 0 },
        "full_score": { "type": "number", "description": "满分", "minimum": 0 },
        "score_summary": { "type": "string", "description": "得分摘要(如 85.5/100" },
        "score_percent": { "type": "number", "description": "得分百分比", "minimum": 0, "maximum": 100 }
      }
    }
  }
}

返回字段说明

顶层字段
字段 类型 必填 默认值 说明
total int - 总文档数(按版本分组后的唯一文档数)
page int - 当前页码
page_size int - 每页数量
total_pages int - 总页数
documents array [] 文档列表
documents 数组元素(当前版本信息)

基本信息字段:

字段 类型 必填 可空 说明
id int 当前版本的文档ID
name string 文档名称
path string 文件存储路径
version_number int 当前版本号(最大值,从1开始)
created_at datetime 创建时间(ISO 8601格式)
status string 文档处理状态,枚举值见下方
file_size int 文件大小(字节)
document_number string 文书号/文档编号,可能为 null
type_id int 文档类型ID
type_name string 文档类型名称
upload_time datetime 上传时间(ISO 8601格式)

任务内评查状态字段:

字段 类型 必填 说明
audit_status int 任务内评查完成状态(来自 cross_task_document_mapping 表):
0 = 未评查
1 = 已评查

⚠️ 注意:此字段与 documents 表的 audit_status(-2~2 的审核状态)含义不同!本接口返回的是任务内的评查完成状态。

评查统计字段:

字段 类型 必填 默认值 说明
total_evaluation_points int 0 总评查点数
pass_count int 0 通过的评查点数量
warning_count int 0 警告的评查点数量(severity=warning或info
error_count int 0 错误的评查点数量(severity=error
manual_count int 0 需人工审核的评查点数量(post_action=manual
issue_count int 0 问题总数(warning_count + error_count

评查消息列表字段:

字段 类型 必填 默认值 说明
warning_messages array[string] [] 警告消息列表
error_messages array[string] [] 错误消息列表
issue_messages array[string] [] 问题消息列表(综合)
manual_messages array[string] [] 需人工确认的消息列表

分数信息字段(交叉评查特有):

字段 类型 必填 默认值 说明
final_score float 0 最终得分(经过提案投票调整后)
full_score float 0 满分
score_summary string "0/0" 得分摘要(如 "85.5/100"
score_percent float 0 得分百分比(0-100

版本信息字段:

字段 类型 必填 默认值 说明
total_versions int 1 总版本数
history_versions array [] 历史版本列表
history_versions 数组元素
字段 类型 必填 可空 说明
id int 历史版本的文档ID
version_number int 版本号(从1开始,数字越小越旧)
created_at datetime 创建时间
upload_time datetime 上传时间(与created_at相同)
file_size int 文件大小(字节)
path string 文件存储路径
status string 文档处理状态
document_number string 文书号/文档编号
type_id int 文档类型ID
type_name string 文档类型名称
audit_status int 评查完成状态(0=未评查,1=已评查),来自 cross_task_document_mapping 表
total_evaluation_points int 总评查点数
pass_count int 通过的评查点数量
warning_count int 警告的评查点数量
error_count int 错误的评查点数量
manual_count int 需人工审核的评查点数量
issue_count int 问题总数(warning_count + error_count
warning_messages array 警告消息列表
error_messages array 错误消息列表
issue_messages array 问题消息列表(warning + error 合并去重)
manual_messages array 需人工审核的消息列表
final_score float 最终得分
full_score float 满分
score_summary string 得分摘要(如 "85.5/100"
score_percent float 得分百分比(0-100

枚举值定义

status 文档处理状态
说明
Waiting 等待处理
Cutting 正在切分
Extractioning 正在提取
Evaluationing 正在评查
Failed 处理失败
Processed 处理完成
audit_status 评查完成状态

重要说明:本接口的 audit_status 字段来源于 cross_task_document_mapping 表, 表示该文档在交叉评查任务中的评查完成状态,而 documents 表中的文档审核状态。

说明 场景
0 未评查 任务内该文档尚未完成评查(默认状态)
1 已评查 任务内该文档已确认完成评查

与 documents.audit_status 的区别

字段来源 字段含义 取值范围
cross_task_document_mapping.audit_status 任务内评查完成状态 0(未评查)、1(已评查)
documents.audit_status 文档整体审核状态 -2(警告)、-1(不通过)、0(待审核)、1(通过)、2(审核中)

错误码

状态码 错误信息 说明
403 无权访问任务:您不是该任务的参与者 用户不是任务参与者

/admin/versions/documents-list 的字段对照

本接口字段 versions接口字段 说明
id id 一致
name name 一致
path path 一致
version_number version_number 一致
created_at created_at 一致
status status 一致
file_size file_size 一致
document_number document_number 一致
type_id type_id 一致
type_name type_name 一致
upload_time upload_time 一致
audit_status audit_status 一致
total_evaluation_points total_evaluation_points 一致
pass_count pass_count 一致
warning_count warning_count 一致
error_count error_count 一致
manual_count manual_count 一致
issue_count issue_count 一致
warning_messages warning_messages 一致
error_messages error_messages 一致
issue_messages issue_messages 一致
manual_messages manual_messages 一致
total_versions total_versions 一致
history_versions history_versions 一致
final_score - 交叉评查特有
full_score - 交叉评查特有
score_summary - 交叉评查特有
score_percent - 交叉评查特有

前端使用说明

  1. 版本展示: 前端可复用版本管理模块的组件,根据 total_versions > 1 判断是否显示版本展开按钮
  2. 历史版本折叠: 默认只显示当前版本,历史版本通过折叠面板展开
  3. 继续上传: 在任务详情页提供"继续上传"入口,上传同名文件会自动归纳为新版本
  4. 评查统计: 当前版本和历史版本都有独立的评查统计,可对比不同版本的改进情况
  5. 审核状态: 每个版本有独立的 audit_status,历史版本的审核状态也可以查看


10. 确认完成文档评查

POST /api/v2/cross_review/tasks/{task_id}/documents/{document_id}/complete

确认完成对指定文档的评查,更新任务进度。仅任务创建者或主要负责人可调用

权限: cross_review:document:complete

路径参数

参数 类型 说明
task_id int 任务ID(必须大于0
document_id int 文档ID(必须大于0

请求体

无需请求体。

业务逻辑

  1. 任务存在性验证
    • 验证任务存在
  2. 权限验证(重要!)
    • 只有以下用户有权确认完成
      • 任务创建者(assigner_id
      • 主要负责人(principal_user_ids 数组中的用户)
    • 普通任务参与者(仅在 user_ids 中)无权确认完成
    • 建议前端先调用 GET /tasks/{task_id}/can-confirm 判断是否显示按钮
  3. 文档归属验证
    • 验证文档确实属于该任务(在 cross_task_document_mapping 中)
  4. 状态更新
    • cross_task_document_mapping.audit_status 设置为 1(已完成)

响应示例

{
  "code": 0,
  "success": true,
  "message": "文档评查已完成",
  "task_id": 1,
  "document_id": 123
}

错误码

状态码 错误信息 说明
400 任务不存在 无效的任务ID
400 文档不在任务中 文档不属于该任务
403 用户不是任务创建者或主要负责人,无权确认完成 权限不足


12. 为任务文档追加附件

接口路径: POST /api/v2/cross_review/tasks/{task_id}/documents/{document_id}/append_attachments

接口说明: 为交叉评查任务中的文档追加附件,创建新版本。

设计方案

核心逻辑

  • 追加附件后创建新文档(新版本),原文档保留
  • 新文档自动关联到当前任务(cross_task_document_mapping
  • 新文档 audit_status = 0(需重新评查)
  • 原文档的评查结果、提案、投票保持不变
  • 新文档保持相同的 name + type_id,触发版本分组逻辑

复用模块

  • 底层调用现有的 append_contract_attachments 服务
  • merge_mode 强制为 "new"(创建新文档)

请求参数

Path 参数

字段 类型 必填 说明
task_id int 任务ID
document_id int 原文档ID

Form 参数

字段 类型 必填 默认值 说明
files File[] - 附件列表(支持 PDF/Word/ZIP/RAR
remark string null 备注说明
use_markdown bool false Word附件是否使用Markdown处理(详见下方说明)

use_markdown 参数详解

作用:控制 Word 附件(.doc/.docx)的处理方式。

use_markdown Word 附件处理方式 适用场景
false(默认) Word → 转 PDF → OCR 识别 需要保留原始排版/图片
true Word → 直接 Markdown 提取文本 文本为主,避免 OCR 错误

处理逻辑

判断原合同类型(is_original_word = 原合同是 .doc/.docx

处理 Word 附件时:
├── if 原合同是 Word OR use_markdown=true:
│   → Word 附件直接用 Markdown 流程提取文本
│   → 跳过 OCR,准确率更高
└── else:
    → Word 转 PDF,走 OCR 流程
    → 保留原始排版和图片

为什么要用 Markdown 处理?

对比项 OCR 方式 Markdown 方式
准确率 可能有识别错误 100%(直接提取)
处理速度 较慢(需转PDF+OCR 快(直接解析)
格式保留 保留排版/图片 保留表格/列表结构
适用场景 扫描件、图片多 纯文本、表格多

建议

  • 原合同是 Word 文件时,自动走 Markdown(无需设置)
  • 原合同是 PDF + Word 附件时,根据附件内容选择:
    • 附件以文本/表格为主 → use_markdown=true
    • 附件有重要图片/特殊排版 → use_markdown=false

权限要求

  • 当前用户必须是任务创建者assigner_id)或主要负责人principal_user_ids 数组中)
  • 需要权限:cross_review:document:append

⚠️ 注意:普通任务参与者无权追加附件,只有任务创建者或主要负责人可以操作。

响应示例

{
  "success": true,
  "result": {
    "original_document_id": 2200,
    "new_document_id": 2201,
    "new_version_number": 3,
    "task_id": 123,
    "message": "附件追加成功,已创建新版本",
    "background_processing": true
  }
}

处理流程

1. 权限校验
   ├── 验证 task_id 存在
   ├── 验证 current_user 是任务创建者或主要负责人
   └── 验证 document_id 在任务的文档映射中

2. 调用现有追加附件模块
   ├── merge_mode = "new"(强制创建新文档)
   ├── 复用 PDF 合并 / Word Markdown 处理逻辑
   └── 上传到 MinIO

3. 更新任务-文档映射
   ├── 新文档添加到 cross_task_document_mapping
   ├── 新文档 audit_status = 0
   └── 原文档映射保留不变

4. 触发后台处理
   └── Celery 任务:OCR → 抽取 → 评查


13. 任务模板对比功能

功能说明: 为交叉评查任务绑定合同模板,支持任务下所有合同与模板进行结构对比。

设计方案

关键点

模板不是 documents 表的记录,是直接存储在 contract_structure_comparison 表中的:

  • template_contract_path - 模板文件路径
  • template_contract_name - 模板文件名
  • ocr_results - 模板OCR结果
问题分析:交叉评查需求 vs 现有设计
维度 现有设计 交叉评查需求
关系 1个源文档 : 1条模板记录 N个文档共用1个模板
模板复用 模板信息内嵌在记录中 需要模板复用
document_id 指向源文档(必填) 任务级别,不绑定单个文档
采用方案:直接复用(方案A

优点

  • 不改动 contract_structure_comparison 表结构
  • 复用现有的模板OCR、对比服务
  • 实现简单

数据库变更

-- 任务表添加字段,指向"模板记录"
ALTER TABLE cross_examination_tasks
ADD COLUMN template_comparison_id INTEGER
REFERENCES contract_structure_comparison(id);

COMMENT ON COLUMN cross_examination_tasks.template_comparison_id
IS '任务关联的合同模板记录ID,用于结构对比';

复用表contract_structure_comparison(不新建表)

实现流程

  1. 上传模板时,用任务中第一个文档的 document_id 创建 contract_structure_comparison 记录
  2. 任务表保存 template_comparison_id 指向该记录
  3. 对比时,读取模板记录的 ocr_results,复用现有对比逻辑

13.1 上传/绑定模板到任务

接口路径: POST /api/v2/cross_review/tasks/{task_id}/template

请求参数

字段 类型 必填 说明
file File 模板合同文件(PDF/Word

响应示例

{
  "success": true,
  "result": {
    "task_id": 123,
    "template_comparison_id": 190,
    "template_name": "设备维保合同范本.pdf",
    "template_path": "documents/mz/合同对比模板/2025/12/...",
    "status": "Waiting",
    "message": "模板上传成功,正在进行OCR处理"
  }
}

处理流程

1. 权限校验(任务创建者或主要负责人)
2. 获取任务中第一个文档的 document_id
3. 调用现有 upload_contract_template 逻辑
   └── 创建 contract_structure_comparison 记录
4. 更新任务表 template_comparison_id
5. 触发模板OCR任务(Celery

13.2 获取任务模板信息

接口路径: GET /api/v2/cross_review/tasks/{task_id}/template

响应示例

{
  "success": true,
  "result": {
    "task_id": 123,
    "template_comparison_id": 190,
    "template_name": "设备维保合同范本.pdf",
    "template_path": "documents/mz/合同对比模板/2025/12/...",
    "status": "Processed",
    "ocr_completed": true,
    "created_at": "2025-12-13T10:00:00Z"
  }
}

13.3 触发文档与模板对比

接口路径: POST /api/v2/cross_review/tasks/{task_id}/documents/{document_id}/compare_template

响应示例

{
  "success": true,
  "result": {
    "task_id": 123,
    "document_id": 2200,
    "comparison_id": 191,
    "status": "Processing",
    "message": "对比任务已提交,请稍后查询结果"
  }
}

处理流程

1. 权限校验(任务参与者)
2. 验证任务已绑定模板(template_comparison_id 不为空)
3. 验证模板OCR已完成(status = 'Processed'
4. 获取文档的 ocr_result
5. 获取模板的 ocr_results
6. 调用 contract_llm_compare() 执行对比
7. 保存对比结果到新的 contract_structure_comparison 记录

13.4 获取对比结果

接口路径: GET /api/v2/cross_review/tasks/{task_id}/documents/{document_id}/comparison_result

响应示例

{
  "success": true,
  "result": {
    "document_id": 2200,
    "document_name": "车辆维保合同.pdf",
    "template_name": "设备维保合同范本.pdf",
    "status": "Processed",
    "comparison_results": {
      "合同正文": [
        {
          "field": "甲方签字",
          "source_page": "11",
          "template_page": "11",
          "status": "normal",
          "details": "甲方签字字段存在(源合同第11页,模板第11页)"
        },
        {
          "field": "合同有效期",
          "source_page": "",
          "template_page": "10",
          "status": "missing_field",
          "details": "源合同缺少合同有效期字段"
        }
      ],
      "合同落款": []
    },
    "summary": {
      "total_fields": 45,
      "matched_fields": 42,
      "missing_fields": 3,
      "match_rate": 93.3
    }
  }
}

13.5 批量对比任务下所有文档

接口路径: POST /api/v2/cross_review/tasks/{task_id}/compare_all

响应示例

{
  "success": true,
  "result": {
    "task_id": 123,
    "total_documents": 5,
    "submitted": 5,
    "message": "已提交5个文档的对比任务"
  }
}

架构图

┌─────────────────────────────────────────────────────────────────┐
│                    模板对比功能架构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  cross_examination_tasks                                        │
│  ┌───────────────────────────────┐                              │
│  │ id: 123                       │                              │
│  │ task_name: "12月合同评查"      │                              │
│  │ template_comparison_id: 190 ──┼──────────┐                   │
│  └───────────────────────────────┘          │                   │
│                                             ▼                   │
│                              contract_structure_comparison      │
│                              ┌─────────────────────────────┐    │
│                              │ id: 190 (模板主记录)         │    │
│                              │ document_id: 2200 (代表文档) │    │
│                              │ template_contract_path: ... │    │
│                              │ template_contract_name: ... │    │
│                              │ ocr_results: {...} (模板OCR) │    │
│                              │ status: "Processed"         │    │
│                              └─────────────────────────────┘    │
│                                                                 │
│  对比时为每个文档创建独立记录:                                   │
│  ┌─────────────────────────────┐                                │
│  │ id: 191 (文档2200的对比结果) │                                │
│  │ document_id: 2200           │                                │
│  │ comparison_results: {...}   │                                │
│  └─────────────────────────────┘                                │
│  ┌─────────────────────────────┐                                │
│  │ id: 192 (文档2201的对比结果) │                                │
│  │ document_id: 2201           │                                │
│  │ comparison_results: {...}   │                                │
│  └─────────────────────────────┘                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

权限要求

接口 权限
上传模板 cross_review:template:upload(任务创建者/主要负责人)
获取模板信息 cross_review:template:read(任务参与者)
触发对比 cross_review:template:compare(任务参与者)
获取对比结果 cross_review:template:read(任务参与者)
批量对比 cross_review:template:compare(任务参与者)

权限说明

  • 上传模板:只有任务创建者或主要负责人可以为任务上传/更换模板
  • 对比操作:所有任务参与者都可以触发对比、查看对比结果