diff --git a/docs/功能设计/1.自动汇总.md b/docs/功能设计/1.自动汇总.md new file mode 100644 index 0000000..d6bf121 --- /dev/null +++ b/docs/功能设计/1.自动汇总.md @@ -0,0 +1,597 @@ +# 自动汇总文件夹文件目录与页数流程功能设计 + +## 文档信息 + +| 项目 | 内容 | +| --- | --- | +| 需求分析文档 | docs/需求分析/1.自动汇总.md | +| 功能名称 | 自动汇总文件夹文件目录与页数 | +| 所属模块 | 审核智能体 review_agent | +| 设计日期 | 2026-06-05 | +| 设计版本 | V1.0 | + +--- + +## 一、设计目标 + +本功能面向试剂盒 NMPA 注册申报资料审核场景,支持用户在 AI 对话页上传压缩包或多个文件,由后台异步执行文件汇总工作流,自动完成解压、文件清单扫描、页数统计、产品名识别、Markdown 报告生成和 Excel 导出。 + +前台 AI 对话页需要展示一个工作流卡片,实时呈现“上传中、解压中、扫描中、解析中、识别中、输出中、完成/失败”等节点状态;工作流完成后,AI 对话框展示 Markdown 简表,并提供 Markdown 报告和 Excel 明细下载链接。上传文件、批次、节点状态、文件明细和导出结果均需要与当前对话绑定,一个对话对应一套文件,不能跨对话串用。 + +--- + +## 二、总体架构 + +### 2.1 架构原则 + +| 原则 | 说明 | +| --- | --- | +| 对话绑定 | 上传文件、处理批次、结果文件均绑定当前 Conversation | +| 按需加载 | 将文件处理流程拆分为多个可单独执行的 Skill,按工作流节点调用 | +| 后台异步 | 用户提交后后台执行工作流,前台通过 SSE 接收状态事件 | +| 失败隔离 | 解压失败导致批次失败;单文件解析失败最多重试 3 次后记录异常并继续 | +| 可迁移 MCP | Demo 阶段使用项目内 Skill 注册与调用,后续可迁移为 MCP Tool | +| 可追溯 | 每个节点状态、文件统计结果、导出文件均持久化入库 | + +### 2.2 逻辑架构 + +```mermaid +flowchart TD + A["AI 对话页"] --> B["上传接收接口"] + B --> C["工作流任务表"] + C --> D["后台工作流执行器"] + D --> E["Skill 注册表"] + E --> F1["上传接收 Skill"] + E --> F2["压缩包解压 Skill"] + E --> F3["文件清单扫描 Skill"] + E --> F4["文档页数统计 Skill"] + E --> F5["产品信息识别 Skill"] + E --> F6["汇总报告生成 Skill"] + E --> F7["Excel 导出 Skill"] + D --> G["数据库存档"] + D --> H["导出文件存储"] + D --> I["SSE 状态事件"] + I --> A +``` + +### 2.3 技术选型 + +| 设计项 | Demo 方案 | 后续演进 | +| --- | --- | --- | +| 工作流编排 | 项目内 LangGraph 风格节点图执行器 | 接入 LangGraph | +| Skill 形态 | Python 类或函数注册表,按节点名称动态调用 | 封装为 MCP Tool | +| 后台任务 | Django 后台线程 + 数据库状态 | Celery/RQ + Redis | +| 实时更新 | 沿用现有 SSE 流式能力,新增 workflow 事件 | 独立任务事件通道 | +| 文件存储 | 本地 media 目录 | 对象存储或加密文件服务 | +| Markdown 渲染 | 前端引入安全 Markdown 渲染 | 统一富文本渲染组件 | + +--- + +## 三、工作流设计 + +### 3.1 节点图 + +```mermaid +flowchart LR + N1["上传中"] --> N2{"是否压缩包"} + N2 -->|"是"| N3["解压中"] + N2 -->|"否"| N4["扫描中"] + N3 --> N4 + N4 --> N5["解析页数中"] + N5 --> N6["识别产品名中"] + N6 --> N7["输出中"] + N7 --> N8["完成"] + N3 -->|"解压失败"| F["失败"] + N7 -->|"导出失败"| F +``` + +### 3.2 节点定义 + +| 节点编码 | 节点名称 | 触发 Skill | 成功条件 | 失败处理 | +| --- | --- | --- | --- | --- | +| upload | 上传中 | 上传接收 Skill | 原始文件保存成功,批次创建成功 | 批次失败 | +| extract | 解压中 | 压缩包解压 Skill | zip/rar/7z 等压缩包解压成功 | 批次失败 | +| inventory | 扫描中 | 文件清单扫描 Skill | 生成文件清单 | 批次失败或空文件提示 | +| page_count | 解析页数中 | 文档页数统计 Skill | 支持类型完成页数统计或异常记录 | 单文件失败不阻断 | +| product_detect | 识别产品名中 | 产品信息识别 Skill | 识别到产品名或返回空 | 不阻断 | +| report | 输出中 | 汇总报告生成 Skill | Markdown 报告与对话简表生成成功 | 批次失败 | +| export | 输出中 | Excel 导出 Skill | Excel 明细生成成功 | 批次失败或记录导出异常 | +| completed | 完成 | 工作流执行器 | 所有必需产物完成 | 写入完成状态 | + +### 3.3 状态机 + +| 状态 | 含义 | +| --- | --- | +| pending | 已创建,等待执行 | +| running | 执行中 | +| retrying | 单文件解析失败,正在重试 | +| success | 节点执行成功 | +| failed | 节点或批次失败 | +| skipped | 当前节点不需要执行,例如非压缩包跳过解压 | + +--- + +## 四、Skill 设计 + +### 4.1 Skill 注册与调用 + +Demo 阶段在项目内定义 Skill 注册表,工作流执行器根据节点编码按需加载并执行对应 Skill。 + +```text +WorkflowExecutor +-> 根据当前节点读取 Skill 名称 +-> 从 SkillRegistry 获取 Skill +-> 执行 skill.run(context) +-> 写入节点状态与结果 +-> 发送 SSE 状态事件 +-> 进入下一节点 +``` + +后续 MCP 化时,每个 Skill 可映射为独立 MCP Tool,输入输出保持稳定 JSON 契约。 + +### 4.2 上传接收 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | 上传接收 Skill | +| 职责 | 接收对话页上传的压缩包或多个文件,保存原始文件,创建上传批次 | +| 输入 | conversation_id、user_id、uploaded_files | +| 输出 | batch_id、upload_file_ids、upload_type、original_storage_paths | +| 数据写入 | FileSummaryBatch、UploadedSourceFile | +| 关键规则 | 文件必须绑定当前 Conversation;同一对话只使用本对话上传的文件 | + +### 4.3 压缩包解压 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | 压缩包解压 Skill | +| 职责 | 识别并解压 zip、rar、7z 等常见压缩包,保留目录结构 | +| 输入 | batch_id、source_file_path | +| 输出 | extract_root、extracted_file_count | +| 数据写入 | WorkflowNodeRun、批次工作目录 | +| 关键规则 | 防止路径穿越;解压目录必须限定在批次工作目录内;解压失败批次失败 | + +### 4.4 文件清单扫描 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | 文件清单扫描 Skill | +| 职责 | 遍历解压目录或散装文件目录,生成文件清单 | +| 输入 | batch_id、scan_root | +| 输出 | inventory_items | +| 数据写入 | FileSummaryItem | +| 关键规则 | 保留目录层级;散装文件归入同一批次根目录;隐藏文件和空文件可标记跳过 | + +### 4.5 文档页数统计 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | 文档页数统计 Skill | +| 职责 | 对支持类型统计页数或数量 | +| 输入 | batch_id、FileSummaryItem 列表 | +| 输出 | page_count、statistics_status、error_message | +| 数据写入 | FileSummaryItem | +| 关键规则 | 支持 pdf、doc、docx、xls、xlsx、ppt、pptx;单文件失败最多重试 3 次,仍失败则记录异常并继续 | + +页数统计口径: + +| 文件类型 | 统计口径 | +| --- | --- | +| pdf | PDF 页面数量 | +| doc/docx | 优先转 PDF 后统计页面数量 | +| xls/xlsx | Demo 阶段按工作表数量统计 | +| ppt/pptx | 按幻灯片数量统计 | + +### 4.6 产品信息识别 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | 产品信息识别 Skill | +| 职责 | 尝试识别产品名称,并用于更新对话标题 | +| 输入 | batch_id、文件名、目录名、可读取文本片段 | +| 输出 | product_name、confidence、evidence | +| 数据写入 | FileSummaryBatch.product_name、Conversation.title | +| 关键规则 | 优先从文件名和目录名识别;其次读取文档首页或关键文本;识别失败不阻断流程 | + +会话标题规则: + +| 场景 | 标题处理 | +| --- | --- | +| 识别到产品名 | 更新为“产品名-文件汇总” | +| 未识别产品名 | 保持原对话标题 | +| 用户已手动命名 | 可保留用户标题,产品名写入批次信息 | + +### 4.7 汇总报告生成 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | 汇总报告生成 Skill | +| 职责 | 生成完整 Markdown 报告和对话框展示简表 | +| 输入 | batch_id、统计摘要、文件明细、异常清单 | +| 输出 | markdown_report_path、assistant_markdown_summary | +| 数据写入 | ExportedSummaryFile、Message | +| 关键规则 | Markdown 简表需要适合前端对话框渲染;完整报告包含全部文件明细 | + +### 4.8 Excel 导出 Skill + +| 项目 | 说明 | +| --- | --- | +| 中文名称 | Excel 导出 Skill | +| 职责 | 生成 Excel 汇总文件 | +| 输入 | batch_id、统计摘要、文件明细 | +| 输出 | excel_path、download_url | +| 数据写入 | ExportedSummaryFile | +| 关键规则 | 至少包含“汇总信息”“文件明细”两个 Sheet | + +--- + +## 五、数据模型设计 + +### 5.1 FileSummaryBatch + +文件汇总批次,表示一次对话内的文件汇总任务。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| id | BigAutoField | 主键 | +| conversation | ForeignKey(Conversation) | 绑定对话 | +| user | ForeignKey(User) | 上传用户 | +| batch_no | CharField | 批次编号 | +| product_name | CharField | 识别出的产品名,可为空 | +| upload_type | CharField | archive、multi_file | +| status | CharField | pending、running、success、failed | +| total_files | Integer | 文件总数 | +| supported_files | Integer | 支持统计的文件数 | +| success_files | Integer | 统计成功数 | +| failed_files | Integer | 统计失败数 | +| unsupported_files | Integer | 不支持文件数 | +| total_pages | Integer | 总页数 | +| work_dir | CharField | 批次工作目录 | +| error_message | TextField | 批次异常说明 | +| created_at | DateTimeField | 创建时间 | +| started_at | DateTimeField | 开始时间 | +| finished_at | DateTimeField | 完成时间 | + +### 5.2 UploadedSourceFile + +上传原始文件记录。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| id | BigAutoField | 主键 | +| batch | ForeignKey(FileSummaryBatch) | 所属批次 | +| original_name | CharField | 原始文件名 | +| storage_path | CharField | 保存路径 | +| file_size | BigInteger | 文件大小 | +| content_type | CharField | MIME 类型 | +| created_at | DateTimeField | 上传时间 | + +### 5.3 FileSummaryItem + +文件明细记录。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| id | BigAutoField | 主键 | +| batch | ForeignKey(FileSummaryBatch) | 所属批次 | +| file_index | Integer | 文件序号 | +| directory_level | CharField | 目录层级 | +| file_name | CharField | 文件名 | +| file_type | CharField | 文件类型 | +| relative_path | CharField | 相对路径 | +| storage_path | CharField | 实际处理路径 | +| page_count | Integer | 页数,可为空 | +| statistics_status | CharField | success、failed、unsupported、skipped | +| retry_count | Integer | 页数统计重试次数 | +| error_message | TextField | 异常说明 | +| created_at | DateTimeField | 创建时间 | +| updated_at | DateTimeField | 更新时间 | + +### 5.4 WorkflowNodeRun + +工作流节点运行记录。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| id | BigAutoField | 主键 | +| batch | ForeignKey(FileSummaryBatch) | 所属批次 | +| node_code | CharField | 节点编码 | +| node_name | CharField | 节点名称 | +| status | CharField | pending、running、retrying、success、failed、skipped | +| progress | Integer | 进度百分比 | +| message | TextField | 节点提示 | +| started_at | DateTimeField | 开始时间 | +| finished_at | DateTimeField | 完成时间 | + +### 5.5 ExportedSummaryFile + +导出文件记录。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| id | BigAutoField | 主键 | +| batch | ForeignKey(FileSummaryBatch) | 所属批次 | +| export_type | CharField | markdown、excel | +| file_name | CharField | 文件名 | +| storage_path | CharField | 保存路径 | +| download_url | CharField | 下载链接 | +| status | CharField | success、failed | +| error_message | TextField | 导出异常说明 | +| created_at | DateTimeField | 生成时间 | + +--- + +## 六、后端接口设计 + +### 6.1 上传并启动工作流 + +| 项目 | 内容 | +| --- | --- | +| URL | POST /api/review-agent/conversations/{conversation_id}/file-summary/start/ | +| 认证 | 登录用户 | +| 请求类型 | multipart/form-data | +| 请求参数 | files[]、prompt | +| 响应 | batch_id、status、workflow_nodes | + +处理逻辑: + +```text +校验 conversation 属于当前用户 +-> 保存上传文件 +-> 创建 FileSummaryBatch +-> 创建 WorkflowNodeRun 初始节点 +-> 启动后台线程执行工作流 +-> 返回 batch_id 和初始节点状态 +``` + +### 6.2 工作流 SSE 事件流 + +| 项目 | 内容 | +| --- | --- | +| URL | GET /api/review-agent/file-summary/{batch_id}/events/ | +| 认证 | 登录用户 | +| 响应类型 | text/event-stream | + +事件类型: + +| 事件 | 说明 | +| --- | --- | +| workflow_started | 工作流开始 | +| node_started | 节点开始 | +| node_progress | 节点进度更新 | +| node_retrying | 单文件解析重试 | +| node_completed | 节点完成 | +| node_failed | 节点失败 | +| workflow_completed | 工作流完成 | +| workflow_failed | 工作流失败 | + +示例: + +```json +{ + "batch_id": 12, + "node_code": "page_count", + "node_name": "解析页数中", + "status": "retrying", + "progress": 42, + "message": "检测报告.pdf 解析失败,正在第 2 次重试" +} +``` + +### 6.3 查询批次状态 + +| 项目 | 内容 | +| --- | --- | +| URL | GET /api/review-agent/file-summary/{batch_id}/ | +| 认证 | 登录用户 | +| 响应 | 批次摘要、节点状态、文件简表、导出文件链接 | + +用途: + +| 场景 | 说明 | +| --- | --- | +| 页面刷新恢复 | 前端重新加载后恢复工作流卡片状态 | +| 历史记录查看 | 从会话历史进入后展示已完成汇总结果 | + +### 6.4 下载导出文件 + +| 项目 | 内容 | +| --- | --- | +| URL | GET /api/review-agent/file-summary/exports/{export_id}/download/ | +| 认证 | 登录用户 | +| 响应 | 文件流 | + +权限规则: + +```text +export_id -> batch -> conversation -> user +必须等于当前登录用户,才允许下载。 +``` + +--- + +## 七、前端设计 + +### 7.1 AI 对话页改造 + +现有 `templates/home.html` 和 `static/js/app.js` 需要增强: + +| 改造点 | 说明 | +| --- | --- | +| 附件选择 | 在输入框旁增加文件上传按钮,支持压缩包和多个文件 | +| 工作流卡片 | 用户提交后在对话流中插入工作流状态卡片 | +| SSE 监听 | 监听后台节点事件,实时更新卡片节点状态 | +| Markdown 渲染 | AI 回复支持 Markdown 表格和下载链接渲染 | +| 状态恢复 | 页面刷新后查询批次状态,恢复工作流卡片 | + +### 7.2 工作流卡片 + +卡片包含节点列表: + +| 节点 | 前台展示文案 | +| --- | --- | +| upload | 上传中 | +| extract | 解压中 | +| inventory | 扫描中 | +| page_count | 解析页数中 | +| product_detect | 识别产品名中 | +| report/export | 输出中 | +| completed | 已完成 | + +节点状态样式: + +| 状态 | 展示 | +| --- | --- | +| pending | 灰色等待 | +| running | 高亮进行中 | +| retrying | 黄色重试中 | +| success | 绿色完成 | +| failed | 红色失败 | +| skipped | 灰色跳过 | + +### 7.3 对话框结果展示 + +工作流完成后,AI 对话框新增助手消息,内容为 Markdown: + +```markdown +已完成文件目录与页数汇总。 + +| 指标 | 数量 | +| --- | --- | +| 文件总数 | 24 | +| 统计成功 | 21 | +| 统计失败 | 2 | +| 不支持 | 1 | +| 总页数 | 386 | + +| 序号 | 目录层级 | 文件名 | 类型 | 页数 | 状态 | 异常说明 | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | 注册资料/说明书 | 说明书.docx | docx | 12 | 成功 | | +| 2 | 注册资料/检测报告 | 检测报告.pdf | pdf | 38 | 成功 | | + +[下载 Markdown 报告](download-url) +[下载 Excel 明细](download-url) +``` + +--- + +## 八、后台服务设计 + +### 8.1 WorkflowExecutor + +负责批次级工作流编排。 + +| 方法 | 说明 | +| --- | --- | +| start(batch_id) | 启动后台任务 | +| run(batch_id) | 串行执行节点图 | +| run_node(node_code, context) | 执行单个节点 | +| emit_event(batch_id, event_type, payload) | 写入并推送事件 | +| complete(batch_id) | 完成批次 | +| fail(batch_id, error) | 标记批次失败 | + +### 8.2 SkillRegistry + +负责 Skill 注册与按需加载。 + +| 方法 | 说明 | +| --- | --- | +| register(name, skill_cls) | 注册 Skill | +| get(name) | 获取 Skill | +| run(name, context) | 执行 Skill | + +### 8.3 PageCountService + +负责具体文件页数统计。 + +| 方法 | 说明 | +| --- | --- | +| count_pdf(path) | 统计 PDF 页面数 | +| count_word(path) | doc/docx 转 PDF 后统计页面数 | +| count_excel(path) | 统计工作表数量 | +| count_ppt(path) | 统计幻灯片数量 | +| count_with_retry(item, max_retry=3) | 单文件重试统计 | + +### 8.4 ExportService + +负责 Markdown 和 Excel 导出。 + +| 方法 | 说明 | +| --- | --- | +| build_markdown_report(batch) | 生成完整 Markdown 报告 | +| build_chat_summary(batch) | 生成对话简表 | +| build_excel(batch) | 生成 Excel 明细 | +| create_download_record(batch, path, type) | 创建下载记录 | + +--- + +## 九、异常与重试设计 + +### 9.1 批次级失败 + +| 场景 | 处理 | +| --- | --- | +| 上传保存失败 | 批次不创建或标记失败 | +| 压缩包无法解压 | 批次失败,工作流终止 | +| 文件清单为空 | 批次失败,提示未检测到可处理文件 | +| 报告导出失败 | 批次失败或标记导出异常 | + +### 9.2 文件级失败 + +| 场景 | 处理 | +| --- | --- | +| 单文件页数解析失败 | 最多重试 3 次 | +| 重试仍失败 | statistics_status=failed,记录异常说明,继续处理其他文件 | +| 不支持类型 | statistics_status=unsupported,不重试 | +| 加密或损坏文件 | statistics_status=failed,记录“文件加密或损坏” | + +--- + +## 十、安全设计 + +| 设计点 | 说明 | +| --- | --- | +| 对话隔离 | 所有批次查询和下载必须校验 conversation.user | +| 防串文件 | 工作流只能读取当前 batch 绑定的 UploadedSourceFile | +| 解压安全 | 禁止压缩包内路径跳出批次工作目录 | +| 文件执行安全 | 不执行上传文件中的脚本、宏或外部链接 | +| 下载权限 | 下载接口必须验证当前用户拥有批次所属对话 | +| 存储隔离 | 按 user_id/conversation_id/batch_id 建立存储目录 | + +--- + +## 十一、验收设计 + +| 序号 | 验收项 | 验收标准 | +| --- | --- | --- | +| 1 | 对话绑定 | A 对话上传的文件不会出现在 B 对话的汇总结果中 | +| 2 | 压缩包处理 | 支持 zip、rar、7z 常见压缩包解压并保留目录结构 | +| 3 | 多文件处理 | 支持一次上传多个散装文件并生成同一批次结果 | +| 4 | 工作流卡片 | 前台能实时展示上传中、解压中、扫描中、解析中、输出中、完成状态 | +| 5 | 解析重试 | 单文件解析失败最多重试 3 次,失败后记录异常并继续 | +| 6 | Markdown 展示 | 对话框能正确渲染 Markdown 表格和下载链接 | +| 7 | 导出下载 | Markdown 报告和 Excel 明细可通过对话框链接下载 | +| 8 | 数据存档 | 数据库保留批次、上传文件、节点状态、文件明细、导出文件记录 | +| 9 | 标题更新 | 识别到产品名后,可将会话标题更新为“产品名-文件汇总” | + +--- + +## 十二、待确认事项 + +| 序号 | 问题 | 当前建议 | 状态 | +| --- | --- | --- | --- | +| 1 | 是否接入真实 LangGraph 依赖 | Demo 先按 LangGraph 节点图思想自实现轻量编排器 | 待确认 | +| 2 | rar/7z 解压依赖 | 可选 py7zr、rarfile、系统 7z 命令 | 待技术验证 | +| 3 | doc/docx 转 PDF 依赖 | 建议使用 LibreOffice headless | 待技术验证 | +| 4 | 用户手动命名对话时是否允许覆盖 | 建议不覆盖,仅写入产品名字段 | 待确认 | +| 5 | 后台任务是否需要取消能力 | Demo 可不做,正式版建议支持取消 | 待确认 | + +--- + +## 十三、实施建议 + +1. 先补充数据模型和迁移,建立批次、文件明细、节点状态和导出文件表。 +2. 增加上传并启动工作流接口,确保文件和当前对话强绑定。 +3. 实现轻量 WorkflowExecutor 和 SkillRegistry,先完成 zip、pdf、xlsx、pptx 的主链路。 +4. 改造前端对话框,增加附件上传、工作流卡片和 Markdown 渲染。 +5. 补齐 doc/docx、rar、7z 等依赖能力,再完善异常重试和下载权限测试。 diff --git a/docs/开发计划/1.自动汇总.md b/docs/开发计划/1.自动汇总.md new file mode 100644 index 0000000..86e4b96 --- /dev/null +++ b/docs/开发计划/1.自动汇总.md @@ -0,0 +1,634 @@ +# 自动汇总文件夹文件目录与页数流程开发计划 + +## 文档信息 + +| 项目 | 内容 | +| --- | --- | +| 需求分析文档 | docs/需求分析/1.自动汇总.md | +| 功能设计文档 | docs/功能设计/1.自动汇总.md | +| 详细设计文档 | docs/详细设计/1.自动汇总.md | +| 数据库设计文档 | docs/数据库设计/1.自动汇总.md | +| 功能名称 | 自动汇总文件夹文件目录与页数 | +| 所属模块 | 审核智能体 review_agent | +| 执行方式 | 单人开发 + Codex 流水线自动化执行 | +| 计划日期 | 2026-06-05 | +| 计划版本 | V1.0 | + +--- + +## 一、开发计划目标 + +本开发计划用于指导 Codex 按阶段自动完成“自动汇总文件夹文件目录与页数”功能开发。任务拆分按可交付阶段组织,每个任务都需要具备明确目标、涉及文件、前置依赖、开发步骤、验收标准、验证命令和 Codex 执行提示。 + +本功能不按 MVP 缩减范围,必须按需求分析、功能设计、详细设计、数据库设计中的全部范围完成。 + +--- + +## 二、已确认开发规则 + +| 规则项 | 内容 | +| --- | --- | +| 拆分方式 | 按可交付阶段拆分 | +| 任务粒度 | 每个任务写到可直接交给 Codex 执行 | +| 执行对象 | 一个开发者使用 Codex 流水线自动化执行 | +| 单任务范围 | 尽量控制在 1 到 3 类文件 | +| Codex 提示 | 每个任务都提供“Codex 执行提示” | +| 功能范围 | 必须完成全部需求,不允许降级为最小闭环 | +| 前端验证 | 使用 Playwright 做真实浏览器端到端测试 | +| 测试数据 | 测试代码中可动态创建登录用户和临时文件 | +| Git 提交 | 每个阶段完成并验证通过后提交一次 | +| 提交摘要 | 使用执行机器上的 `git-commit-summary` skill | +| 分支规则 | 从 `V2` 创建日期 + 中文功能名分支,完成后合并回 `V2` | + +--- + +## 三、总体验收标准 + +| 类别 | 完成标准 | +| --- | --- | +| 数据库 | 7 张 `ra_` 表全部通过 Django migration 落库,约束、索引、枚举齐全 | +| 上传 | 当前对话右侧上传区支持多文件和压缩包上传,上传即存储,附件不跨对话 | +| 触发 | 用户发送命中提示词后才启动自动汇总工作流,普通对话不误触发 | +| 工作流 | 后台异步执行,节点状态可实时更新,事件可持久化和恢复 | +| 解压 | 支持 zip、7z、rar,解压安全检查必须完成 | +| 统计 | 支持 pdf、doc、docx、xls、xlsx、ppt、pptx,失败重试 3 次,失败不阻断批次 | +| 输出 | 生成 Markdown 报告、Excel 明细,对话框展示 Markdown 简表和下载链接 | +| 前端 | 三栏布局、上方拖拽上传、下方工作流卡片、Markdown 表格渲染正常 | +| 存档 | 批次、附件、文件明细、节点、事件、导出文件全部入库 | +| 标题 | 识别到产品名后按规则更新对话标题 | +| 权限 | 上传、查询、下载都校验当前用户和当前对话 | +| 测试 | 单元、接口、集成、Playwright 端到端测试全部覆盖 | +| 部署 | requirements 可安装,Docker 部署说明包含 7z/p7zip,rar/7z 解压验证通过 | + +--- + +## 四、阶段总览 + +| 阶段 | 名称 | 目标 | 阶段验收 | +| --- | --- | --- | --- | +| P0 | 流水线准备 | 建立开发分支,确认依赖、规范和现状 | 分支创建完成,开发前检查通过 | +| P1 | 数据模型与迁移 | 完成 7 张 ra_ 表 ORM 与 migration | SQLite 可建表,模型约束正确 | +| P2 | 上传与对话绑定 | 实现上传即存储、同名版本和附件权限 | 上传接口可用,附件不跨对话 | +| P3 | 工作流触发与后台执行 | 实现提示词触发、批次创建、后台节点执行和事件持久化 | 命中提示词可启动工作流,状态可查询 | +| P4 | Skill 与文件处理能力 | 实现解压、扫描、页数统计、重试和产品名识别 | 支持格式全部进入处理流程 | +| P5 | 报告生成与下载 | 实现 Markdown 报告、Excel 导出、下载权限和助手消息 | 可下载报告,数据库留痕完整 | +| P6 | 前端三栏与工作流卡片 | 实现右侧上传区、工作流卡片、SSE 更新和 Markdown 渲染 | Playwright 验证前端主流程 | +| P7 | 测试、部署与总体验收 | 补齐自动化测试、端到端测试、Docker 说明和最终合并 | 全部测试通过,合并回 V2 | + +--- + +## 五、P0 流水线准备 + +### FS-P0-001 创建开发分支并检查现状 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | Git / 准备 | +| 前置任务 | 无 | +| 涉及文件 | 无固定文件 | +| 目标 | 从 `V2` 分支创建日期 + 中文功能名开发分支,并确认工作区状态 | +| 开发步骤 | 1. 切换到 `V2`;2. 拉取或确认本地最新状态;3. 创建 `codex/YYYYMMDD-自动汇总文件目录页数` 分支;4. 检查 `git status`;5. 确认已有设计文档存在 | +| 验收标准 | 开发分支创建成功;工作区变更来源清楚;不会覆盖用户已有未提交改动 | +| 验证命令 | `git branch --show-current`; `git status --short` | +| Codex 执行提示 | 请从 `V2` 创建 `codex/YYYYMMDD-自动汇总文件目录页数` 开发分支,检查当前工作区状态,不要回滚用户已有变更。 | + +### FS-P0-002 补充依赖清单与部署前置说明 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 依赖 / 部署准备 | +| 前置任务 | FS-P0-001 | +| 涉及文件 | `requirements.txt`、部署说明文档、前端静态资源引入位置 | +| 目标 | 增加文件解析与导出所需 Python 依赖,并说明 rar/7z 的系统依赖 | +| 开发步骤 | 1. 在 `requirements.txt` 增加 `pypdf`、`python-docx`、`python-pptx`、`openpyxl`、`xlrd`、`olefile`、`py7zr`;2. 在前端任务中明确 `marked + DOMPurify` 通过模板或静态资源引入;3. 在部署说明中写明 Docker 需要安装 7z/p7zip;4. 明确不强制依赖 LibreOffice | +| 验收标准 | Python 依赖可安装;部署说明明确 rar 依赖系统 7z/p7zip;未引入 LibreOffice 强依赖 | +| 验证命令 | `pip install -r requirements.txt` | +| Codex 执行提示 | 请按详细设计补充轻量依赖,并在部署说明中写清 Docker 需安装 7z/p7zip 支持 rar/7z,禁止把 LibreOffice 作为必需依赖。 | + +--- + +## 六、P1 数据模型与迁移 + +### FS-P1-001 新增文件汇总 ORM 模型 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 数据库 / 后端 | +| 前置任务 | P0 | +| 涉及文件 | `review_agent/models.py` | +| 目标 | 新增文件汇总相关 7 个模型和状态枚举 | +| 开发步骤 | 1. 定义 `FileAttachment`;2. 定义 `FileSummaryBatch`;3. 定义 `FileSummaryBatchAttachment`;4. 定义 `FileSummaryItem`;5. 定义 `WorkflowNodeRun`;6. 定义 `WorkflowEvent`;7. 定义 `ExportedSummaryFile`;8. 使用 Django `TextChoices` 管理枚举 | +| 验收标准 | 模型字段、关联、默认值、`db_table`、`indexes`、`constraints` 与数据库设计一致 | +| 验证命令 | `python manage.py check` | +| Codex 执行提示 | 请按 `docs/数据库设计/1.自动汇总.md` 在 `review_agent/models.py` 新增 7 个 `ra_` 表模型,使用 Django ORM、TextChoices、短表名、索引和唯一约束。 | + +### FS-P1-002 生成并验证数据库迁移 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 数据库 / 迁移 | +| 前置任务 | FS-P1-001 | +| 涉及文件 | `review_agent/migrations/` | +| 目标 | 生成 migration 并验证 SQLite 可落表 | +| 开发步骤 | 1. 执行 `makemigrations`;2. 检查 migration 是否只包含本功能相关模型;3. 执行 `migrate`;4. 检查表结构和索引 | +| 验收标准 | migration 可执行;SQLite 中生成 7 张 `ra_` 表;约束和索引生效 | +| 验证命令 | `python manage.py makemigrations review_agent`; `python manage.py migrate`; `python manage.py check` | +| Codex 执行提示 | 请为文件汇总模型生成 Django migration 并执行迁移验证,确保 SQLite 下 7 张 `ra_` 表均可创建。 | + +### FS-P1-003 增加模型级测试 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 测试 / 数据库 | +| 前置任务 | FS-P1-002 | +| 涉及文件 | `tests/test_file_summary_models.py` | +| 目标 | 覆盖附件版本、批次绑定、唯一约束和权限查询基础逻辑 | +| 开发步骤 | 1. 测试同一对话同名附件版本号递增;2. 测试 active 版本切换;3. 测试批次绑定附件唯一;4. 测试同批次 relative_path 唯一;5. 测试导出文件能追溯到用户和对话 | +| 验收标准 | 模型测试全部通过,关键约束失败时能暴露错误 | +| 验证命令 | `pytest tests/test_file_summary_models.py` | +| Codex 执行提示 | 请新增模型级测试,覆盖文件汇总表的版本、绑定、唯一约束和对话隔离规则。 | + +### P1 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | `python manage.py check`; `pytest tests/test_file_summary_models.py` | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| Codex 执行提示 | P1 验证通过后,请调用 `git-commit-summary` 分析本阶段变更,并按其输出提交到当前开发分支。 | + +--- + +## 七、P2 上传与对话绑定 + +### FS-P2-001 实现附件存储服务 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 存储 | +| 前置任务 | P1 | +| 涉及文件 | `review_agent/file_summary/storage.py`、`review_agent/file_summary/constants.py` | +| 目标 | 实现上传文件保存、版本号生成、存储目录生成和逻辑删除基础能力 | +| 开发步骤 | 1. 创建 `file_summary` 目录;2. 实现按 `user/conversation/attachments` 保存文件;3. 实现同名附件版本递增;4. 新版本设为 active 并关闭旧 active;5. 实现路径安全处理 | +| 验收标准 | 上传文件保存到受控目录;附件记录绑定当前用户和对话;同名多版本不覆盖 | +| 验证命令 | `pytest tests/test_file_summary_storage.py` | +| Codex 执行提示 | 请实现文件汇总附件存储服务,保证上传即存储、同名多版本、当前对话绑定和路径安全。 | + +### FS-P2-002 实现附件上传接口 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 接口 | +| 前置任务 | FS-P2-001 | +| 涉及文件 | `review_agent/file_summary/views.py`、`review_agent/file_summary/urls.py`、`config/urls.py` | +| 目标 | 新增对话附件上传接口,支持多文件和压缩包上传 | +| 开发步骤 | 1. 新增 `POST /api/review-agent/conversations/{conversation_id}/attachments/`;2. 校验 conversation 属于 request.user;3. 保存多个文件;4. 返回 attachment 列表;5. 接入 URL | +| 验收标准 | 当前用户只能向自己的对话上传;接口返回附件 ID、文件名、大小、版本和状态 | +| 验证命令 | `pytest tests/test_file_summary_views.py -k upload` | +| Codex 执行提示 | 请新增对话附件上传 API,支持一次上传多个文件,所有附件必须绑定当前 Conversation,禁止跨用户上传。 | + +### FS-P2-003 实现附件列表和删除接口 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 接口 | +| 前置任务 | FS-P2-002 | +| 涉及文件 | `review_agent/file_summary/views.py`、`review_agent/file_summary/urls.py` | +| 目标 | 支持前端右侧上传区展示当前对话附件,并允许逻辑删除 | +| 开发步骤 | 1. 新增当前对话附件列表接口;2. 返回 active 和历史版本信息;3. 新增附件逻辑删除接口;4. 删除时设置 `upload_status=deleted`、`is_active=false` | +| 验收标准 | 附件列表只返回当前对话文件;逻辑删除不影响历史批次追溯 | +| 验证命令 | `pytest tests/test_file_summary_views.py -k attachment` | +| Codex 执行提示 | 请实现当前对话附件列表和逻辑删除接口,支持同名版本展示,删除不得物理移除历史批次需要的文件。 | + +### P2 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | `pytest tests/test_file_summary_storage.py tests/test_file_summary_views.py -k "upload or attachment"` | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| Codex 执行提示 | P2 验证通过后,请调用 `git-commit-summary` 分析本阶段变更,并按其输出提交到当前开发分支。 | + +--- + +## 八、P3 工作流触发与后台执行 + +### FS-P3-001 实现提示词触发判断 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 意图识别 | +| 前置任务 | P2 | +| 涉及文件 | `review_agent/file_summary/services/workflow_trigger.py`、`review_agent/services.py` | +| 目标 | 根据提示词决定是否启动自动汇总工作流 | +| 开发步骤 | 1. 定义触发关键词;2. 判断当前对话是否存在可用 active 附件;3. 命中时返回 workflow 类型;4. 未命中走普通 LLM;5. 命中但无附件时返回提示 | +| 验收标准 | “自动汇总”“文件目录”“页数”等关键词可触发;普通对话不误触发 | +| 验证命令 | `pytest tests/test_file_summary_trigger.py` | +| Codex 执行提示 | 请实现自动汇总工作流触发判断,只有当前对话存在可用附件且提示词命中关键词时才启动工作流。 | + +### FS-P3-002 实现批次创建与附件固化 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 工作流 | +| 前置任务 | FS-P3-001 | +| 涉及文件 | `review_agent/file_summary/workflow.py`、`review_agent/file_summary/storage.py` | +| 目标 | 用户消息触发时创建 FileSummaryBatch,并固化本次使用的附件版本 | +| 开发步骤 | 1. 创建批次编号;2. 创建 `FileSummaryBatch`;3. 绑定 active 附件到中间表;4. 标记附件为 bound;5. 创建初始节点记录 | +| 验收标准 | 同一对话可多次汇总;历史批次绑定历史附件版本;不会读取其他对话文件 | +| 验证命令 | `pytest tests/test_file_summary_workflow.py -k batch` | +| Codex 执行提示 | 请实现批次创建和附件版本固化,确保每次汇总只读取本批次绑定的附件。 | + +### FS-P3-003 实现 WorkflowEvent 与 SSE 事件查询 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / SSE | +| 前置任务 | FS-P3-002 | +| 涉及文件 | `review_agent/file_summary/events.py`、`review_agent/file_summary/views.py` | +| 目标 | 持久化工作流事件,并支持前端按 batch 监听和断点续传 | +| 开发步骤 | 1. 实现事件写入;2. 实现 SSE 格式化;3. 新增 `GET /api/review-agent/file-summary/{batch_id}/events/?after=`;4. 新增批次状态查询接口;5. 校验用户权限 | +| 验收标准 | 节点事件可入库;SSE 可返回事件流;页面刷新可通过状态接口恢复 | +| 验证命令 | `pytest tests/test_file_summary_views.py -k "event or status"` | +| Codex 执行提示 | 请实现工作流事件持久化、事件 SSE 接口和批次状态查询接口,所有查询必须校验当前用户权限。 | + +### FS-P3-004 实现轻量后台工作流执行器 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 工作流 | +| 前置任务 | FS-P3-003 | +| 涉及文件 | `review_agent/file_summary/workflow.py`、`review_agent/file_summary/skills/` | +| 目标 | 实现串行节点图执行器,后台异步执行并更新节点状态 | +| 开发步骤 | 1. 定义节点顺序;2. 实现后台线程启动;3. 实现节点开始、成功、失败、跳过状态;4. 每个节点写入 WorkflowNodeRun;5. 每个节点发送 WorkflowEvent | +| 验收标准 | 命中提示词后可后台创建并推进节点;节点状态可查询;异常能标记批次失败 | +| 验证命令 | `pytest tests/test_file_summary_workflow.py -k executor` | +| Codex 执行提示 | 请实现轻量 WorkflowExecutor,按节点图异步执行文件汇总流程,实时写入节点状态和事件。 | + +### FS-P3-005 接入现有流式聊天接口 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 对话 | +| 前置任务 | FS-P3-004 | +| 涉及文件 | `review_agent/services.py`、`review_agent/views.py` | +| 目标 | 在现有 `stream_chat` 流程中按需启动自动汇总工作流 | +| 开发步骤 | 1. 用户消息入库后判断触发;2. 命中时创建批次并启动后台;3. SSE meta 返回 workflow 信息;4. 对话中返回“已启动工作流”类助手消息或后续由报告生成写入结果;5. 未命中时保持原 LLM 流式逻辑 | +| 验收标准 | 普通聊天不受影响;自动汇总触发后前端可拿到 batch_id;无附件时提示用户先上传 | +| 验证命令 | `pytest tests/test_chat.py tests/test_file_summary_workflow.py -k trigger` | +| Codex 执行提示 | 请把自动汇总触发接入现有流式聊天接口,保证普通 LLM 对话兼容,命中工作流时返回 workflow meta。 | + +### P3 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | `pytest tests/test_file_summary_trigger.py tests/test_file_summary_workflow.py tests/test_file_summary_views.py` | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| Codex 执行提示 | P3 验证通过后,请调用 `git-commit-summary` 分析本阶段变更,并按其输出提交到当前开发分支。 | + +--- + +## 九、P4 Skill 与文件处理能力 + +### FS-P4-001 实现 Skill 基类与注册表 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / Skill | +| 前置任务 | P3 | +| 涉及文件 | `review_agent/file_summary/skills/base.py`、`review_agent/file_summary/skills/registry.py`、`review_agent/file_summary/schemas.py` | +| 目标 | 建立项目内 Skill 注册与调用机制,后续可迁移 MCP | +| 开发步骤 | 1. 定义 `WorkflowContext`;2. 定义 `SkillResult`;3. 定义 `BaseSkill`;4. 实现 `SkillRegistry`;5. 支持按名称获取和执行 Skill | +| 验收标准 | 工作流执行器通过注册表调用 Skill;Skill 输入输出保持 JSON 友好 | +| 验证命令 | `pytest tests/test_file_summary_skills.py -k registry` | +| Codex 执行提示 | 请实现文件汇总 Skill 基类、上下文、统一返回结构和注册表,使工作流节点能按需加载 Skill。 | + +### FS-P4-002 实现压缩包解压 Skill + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 文件处理 | +| 前置任务 | FS-P4-001 | +| 涉及文件 | `review_agent/file_summary/services/archive.py`、`review_agent/file_summary/skills/archive_extract.py` | +| 目标 | 支持 zip、7z、rar 解压,并完成路径穿越防护 | +| 开发步骤 | 1. 实现压缩包识别;2. 使用 `zipfile` 解压 zip;3. 使用 `py7zr` 解压 7z;4. 使用系统 `7z` 解压 rar;5. 检查解压目标路径必须在批次工作目录内;6. 解压失败标记批次失败 | +| 验收标准 | zip、7z、rar 均进入解压流程;恶意路径压缩包被拒绝;解压目录保留层级 | +| 验证命令 | `pytest tests/test_file_summary_archive.py` | +| Codex 执行提示 | 请实现压缩包解压服务和 Skill,必须支持 zip、7z、rar,并对所有解压路径做 target_dir 内部校验。 | + +### FS-P4-003 实现文件清单扫描 Skill + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 文件处理 | +| 前置任务 | FS-P4-002 | +| 涉及文件 | `review_agent/file_summary/services/inventory.py`、`review_agent/file_summary/skills/file_inventory.py` | +| 目标 | 扫描解压目录或散装文件,生成 FileSummaryItem 明细 | +| 开发步骤 | 1. 识别扫描根目录;2. 递归遍历文件;3. 生成相对路径;4. 生成目录层级;5. 标记支持、不支持、空文件或跳过状态;6. 按目录顺序生成 file_index | +| 验收标准 | 文件明细保留目录层级;散装文件进入同一批次根;relative_path 唯一 | +| 验证命令 | `pytest tests/test_file_summary_inventory.py` | +| Codex 执行提示 | 请实现文件清单扫描服务和 Skill,保留目录层级,生成文件序号、相对路径、文件类型和初始统计状态。 | + +### FS-P4-004 实现页数统计服务 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 文件解析 | +| 前置任务 | FS-P4-003 | +| 涉及文件 | `review_agent/file_summary/services/page_count.py` | +| 目标 | 支持 pdf、doc、docx、xls、xlsx、ppt、pptx 页数或数量统计 | +| 开发步骤 | 1. pdf 使用 `pypdf` 统计页面;2. docx 使用 `python-docx` 读取内置页数属性;3. doc 使用 `olefile` 读取 OLE 元数据;4. xlsx 使用 `openpyxl` 统计工作表;5. xls 使用 `xlrd` 统计工作表;6. pptx 使用 `python-pptx` 统计幻灯片;7. ppt 使用 `olefile` 读取元数据;8. 无可靠页数时标记 uncertain | +| 验收标准 | 7 类格式全部有处理分支;读不到页数不崩溃;状态区分 success、failed、unsupported、uncertain | +| 验证命令 | `pytest tests/test_file_summary_page_count.py` | +| Codex 执行提示 | 请实现页数统计服务,覆盖 pdf/doc/docx/xls/xlsx/ppt/pptx,老格式读不到可靠页数时标记 uncertain,不允许中断批次。 | + +### FS-P4-005 实现页数统计 Skill 与三次重试 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / Skill | +| 前置任务 | FS-P4-004 | +| 涉及文件 | `review_agent/file_summary/skills/document_page_count.py`、`review_agent/file_summary/services/page_count.py` | +| 目标 | 对每个支持文件执行页数统计,失败最多重试 3 次 | +| 开发步骤 | 1. 遍历 FileSummaryItem;2. 支持类型调用 page_count 服务;3. 失败重试 3 次;4. 更新 retry_count、statistics_status、page_count、error_message;5. 更新节点进度事件;6. 汇总批次数量 | +| 验收标准 | 单文件失败不阻断其他文件;重试事件可记录;批次统计字段更新正确 | +| 验证命令 | `pytest tests/test_file_summary_page_count.py -k retry` | +| Codex 执行提示 | 请实现文档页数统计 Skill,对单文件解析失败最多重试 3 次,仍失败则记录异常并继续处理其他文件。 | + +### FS-P4-006 实现产品名识别 Skill 与会话标题更新 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 识别 | +| 前置任务 | FS-P4-005 | +| 涉及文件 | `review_agent/file_summary/services/product_detect.py`、`review_agent/file_summary/skills/product_detect.py` | +| 目标 | 从目录名、文件名和少量元数据中识别产品名,并按规则更新对话标题 | +| 开发步骤 | 1. 优先使用顶层目录名;2. 从含“产品”“试剂盒”“说明书”等关键词的文件名提取;3. 尝试读取 docx/PDF 元数据 title;4. 写入 batch.product_name;5. 默认标题时更新 Conversation.title;6. 用户自定义标题不覆盖 | +| 验收标准 | 识别失败不阻断;识别成功后批次记录产品名;默认对话标题可更新为“产品名-文件汇总” | +| 验证命令 | `pytest tests/test_file_summary_product_detect.py` | +| Codex 执行提示 | 请实现产品名识别 Skill,从目录名、文件名和轻量元数据识别产品名,识别成功后按规则更新批次和对话标题。 | + +### P4 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | `pytest tests/test_file_summary_skills.py tests/test_file_summary_archive.py tests/test_file_summary_inventory.py tests/test_file_summary_page_count.py tests/test_file_summary_product_detect.py` | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| Codex 执行提示 | P4 验证通过后,请调用 `git-commit-summary` 分析本阶段变更,并按其输出提交到当前开发分支。 | + +--- + +## 十、P5 报告生成与下载 + +### FS-P5-001 实现 Markdown 报告生成 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 报告 | +| 前置任务 | P4 | +| 涉及文件 | `review_agent/file_summary/services/report.py`、`review_agent/file_summary/skills/summary_report.py` | +| 目标 | 生成完整 Markdown 报告和对话框展示简表 | +| 开发步骤 | 1. 构建统计摘要;2. 构建对话简表;3. 构建完整 Markdown 报告;4. 保存到批次 exports 目录;5. 创建 ExportedSummaryFile;6. 生成助手消息内容 | +| 验收标准 | Markdown 包含汇总信息、统计摘要、文件明细、异常清单、处理说明和下载链接占位 | +| 验证命令 | `pytest tests/test_file_summary_report.py -k markdown` | +| Codex 执行提示 | 请实现 Markdown 报告生成 Skill,完整报告和对话简表必须包含文件序号、目录层级、文件名、类型、页数、路径、状态、异常说明。 | + +### FS-P5-002 实现 Excel 导出 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 导出 | +| 前置任务 | FS-P5-001 | +| 涉及文件 | `review_agent/file_summary/services/export_excel.py`、`review_agent/file_summary/skills/excel_export.py` | +| 目标 | 生成 Excel 明细文件 | +| 开发步骤 | 1. 使用 `openpyxl` 创建 Workbook;2. 创建“汇总信息”Sheet;3. 创建“文件明细”Sheet;4. 写入状态、重试次数和异常说明;5. 保存到 exports 目录;6. 创建 ExportedSummaryFile | +| 验收标准 | Excel 可打开;至少包含两个工作表;字段与需求一致 | +| 验证命令 | `pytest tests/test_file_summary_report.py -k excel` | +| Codex 执行提示 | 请实现 Excel 导出 Skill,生成包含“汇总信息”和“文件明细”两个 Sheet 的汇总文件。 | + +### FS-P5-003 实现导出下载接口 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 下载 | +| 前置任务 | FS-P5-002 | +| 涉及文件 | `review_agent/file_summary/views.py`、`review_agent/file_summary/urls.py` | +| 目标 | 提供 Markdown 和 Excel 文件下载,并校验权限 | +| 开发步骤 | 1. 新增 `GET /api/review-agent/file-summary/exports/{export_id}/download/`;2. 校验 export -> batch -> conversation -> user;3. 返回文件流;4. 设置合适文件名;5. 文件不存在时返回错误 | +| 验收标准 | 当前用户可下载自己的导出文件;不能下载其他用户文件;下载链接可用于 Markdown | +| 验证命令 | `pytest tests/test_file_summary_views.py -k download` | +| Codex 执行提示 | 请实现导出文件下载接口,下载权限必须沿 export -> batch -> conversation -> user 校验。 | + +### FS-P5-004 完成报告 Skill 与工作流衔接 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 后端 / 工作流 | +| 前置任务 | FS-P5-003 | +| 涉及文件 | `review_agent/file_summary/workflow.py`、`review_agent/file_summary/services/report.py` | +| 目标 | 工作流完成后写入助手消息,展示 Markdown 简表和真实下载链接 | +| 开发步骤 | 1. 报告和 Excel 导出完成后生成下载 URL;2. 替换对话简表中的下载链接;3. 创建 assistant Message;4. 标记 batch success;5. 发送 workflow_completed 事件 | +| 验收标准 | 工作流完成后对话中出现 Markdown 简表;下载链接可点击;批次状态成功 | +| 验证命令 | `pytest tests/test_file_summary_workflow.py -k report` | +| Codex 执行提示 | 请把 Markdown 报告、Excel 导出和工作流完成逻辑串起来,完成后向当前对话写入助手消息。 | + +### P5 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | `pytest tests/test_file_summary_report.py tests/test_file_summary_views.py -k download tests/test_file_summary_workflow.py -k report` | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| Codex 执行提示 | P5 验证通过后,请调用 `git-commit-summary` 分析本阶段变更,并按其输出提交到当前开发分支。 | + +--- + +## 十一、P6 前端三栏与工作流卡片 + +### FS-P6-001 改造页面为三栏布局 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 前端 / 布局 | +| 前置任务 | P5 | +| 涉及文件 | `templates/home.html`、实际静态 CSS 文件 | +| 目标 | 在现有对话页增加右侧第三栏,上半部分上传区,下半部分工作流卡片 | +| 开发步骤 | 1. 确认真实静态样式文件路径;2. 调整 workspace 结构;3. 增加 `workflow-panel`;4. 增加 `upload-dropzone`;5. 增加 `workflow-card-list`;6. 保证桌面和移动端不遮挡 | +| 验收标准 | 页面显示左侧会话、中间聊天、右侧上传/工作流三栏;移动端布局可用 | +| 验证命令 | `pytest tests/test_file_summary_e2e.py -k layout` 或 Playwright 对应命令 | +| Codex 执行提示 | 请把审核智能体页面改造成三栏布局,右侧上半部分为拖拽上传区,下半部分为工作流卡片列表,并保持现有聊天能力可用。 | + +### FS-P6-002 实现前端附件上传交互 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 前端 / 上传 | +| 前置任务 | FS-P6-001 | +| 涉及文件 | `static/js/app.js`、`templates/home.html`、实际静态 CSS 文件 | +| 目标 | 支持拖拽或选择多个文件上传,上传成功后展示附件列表 | +| 开发步骤 | 1. 绑定 dropzone;2. 支持点击选择文件;3. 调用附件上传 API;4. 展示文件名、版本、大小和状态;5. 上传失败展示错误 | +| 验收标准 | 上传即存储;前端展示当前对话附件;切换对话不串附件 | +| 验证命令 | Playwright 上传测试 | +| Codex 执行提示 | 请实现右侧上传区前端交互,支持拖拽和选择多个文件,调用附件上传接口并展示当前对话附件列表。 | + +### FS-P6-003 实现工作流卡片与 SSE 更新 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 前端 / 工作流 | +| 前置任务 | FS-P6-002 | +| 涉及文件 | `static/js/app.js`、实际静态 CSS 文件 | +| 目标 | 在发送提示词触发工作流后创建卡片,并根据 SSE 更新节点状态 | +| 开发步骤 | 1. 解析 chat stream 中的 workflow meta;2. 创建 workflow card;3. 连接 batch events SSE;4. 更新节点 pending/running/retrying/success/failed/skipped;5. workflow_completed 后更新完成状态;6. 页面刷新后通过状态接口恢复 | +| 验收标准 | 工作流节点实时更新;刷新页面可恢复;失败状态可见 | +| 验证命令 | Playwright 工作流卡片测试 | +| Codex 执行提示 | 请实现工作流卡片前端逻辑,接收 workflow meta 后连接事件流,实时更新上传、解压、扫描、解析、识别、输出、完成等节点状态。 | + +### FS-P6-004 实现 Markdown 安全渲染 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 前端 / 渲染 | +| 前置任务 | FS-P6-003 | +| 涉及文件 | `templates/home.html`、`static/js/app.js`、静态依赖文件或 CDN 引入 | +| 目标 | 让助手消息支持 Markdown 表格和下载链接渲染 | +| 开发步骤 | 1. 引入 `marked + DOMPurify`;2. 普通用户消息保持 escape;3. 助手消息使用安全 Markdown 渲染;4. 历史消息渲染兼容;5. 下载链接可点击 | +| 验收标准 | Markdown 表格渲染为 HTML table;链接渲染为 a 标签;无明显 XSS 风险 | +| 验证命令 | Playwright Markdown 渲染测试 | +| Codex 执行提示 | 请引入 marked + DOMPurify 实现助手消息安全 Markdown 渲染,确保文件汇总结果表格和下载链接正常显示。 | + +### FS-P6-005 实现 Playwright 端到端测试 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 前端 / E2E | +| 前置任务 | FS-P6-004 | +| 涉及文件 | Playwright 测试文件、测试配置 | +| 目标 | 使用真实浏览器覆盖上传、触发、卡片、渲染、下载和恢复 | +| 开发步骤 | 1. 创建测试用户;2. 登录系统;3. 打开审核智能体页面;4. 上传动态生成的测试文件;5. 发送“自动汇总文件目录与页数”;6. 等待工作流卡片完成;7. 验证 Markdown table 和下载链接;8. 刷新后验证卡片恢复;9. 验证越权访问失败 | +| 验收标准 | Playwright 端到端测试通过;关键页面截图可生成;失败时能定位到具体断言 | +| 验证命令 | Playwright 对应执行命令 | +| Codex 执行提示 | 请使用 Playwright 增加真实浏览器端到端测试,从登录、上传、发送提示词一直验证到报告渲染、下载和刷新恢复。 | + +### P6 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | Playwright 端到端测试 + 相关后端接口测试 | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| Codex 执行提示 | P6 验证通过后,请调用 `git-commit-summary` 分析本阶段变更,并按其输出提交到当前开发分支。 | + +--- + +## 十二、P7 测试、部署与总体验收 + +### FS-P7-001 补齐后端测试矩阵 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 测试 / 后端 | +| 前置任务 | P6 | +| 涉及文件 | `tests/test_file_summary_*.py` | +| 目标 | 覆盖单元、接口、工作流集成和权限隔离 | +| 开发步骤 | 1. 覆盖触发词;2. 覆盖附件版本;3. 覆盖解压安全;4. 覆盖文件扫描;5. 覆盖页数统计;6. 覆盖报告导出;7. 覆盖下载权限;8. 覆盖完整工作流 | +| 验收标准 | 后端文件汇总测试全部通过;失败场景覆盖充分 | +| 验证命令 | `pytest tests/test_file_summary_*.py` | +| Codex 执行提示 | 请补齐文件汇总后端测试矩阵,覆盖单元、接口、工作流集成和权限隔离。 | + +### FS-P7-002 补充部署与 Docker 说明 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 部署 / 文档 | +| 前置任务 | FS-P7-001 | +| 涉及文件 | README 或部署说明文档 | +| 目标 | 写明生产或 Docker 部署时的依赖安装和验证方式 | +| 开发步骤 | 1. 写明 Python 依赖安装;2. 写明 7z/p7zip 安装;3. 写明 rar/7z 验证命令;4. 写明 LibreOffice 非必需、仅未来增强使用;5. 写明 media 文件存储目录 | +| 验收标准 | 部署说明可指导在 Docker 中启用 rar/7z 解压;依赖边界清楚 | +| 验证命令 | `python manage.py check` | +| Codex 执行提示 | 请补充部署说明,明确 Docker 环境需要安装 7z/p7zip 支持 rar/7z,LibreOffice 不是必需依赖。 | + +### FS-P7-003 执行总体验收 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | 验收 / 流水线 | +| 前置任务 | FS-P7-002 | +| 涉及文件 | 无固定文件 | +| 目标 | 运行全部测试和端到端验证,确认功能完整 | +| 开发步骤 | 1. 运行 Django check;2. 运行全量 pytest;3. 运行 Playwright E2E;4. 手工或自动验证下载文件可打开;5. 检查数据库记录;6. 检查 git status | +| 验收标准 | 总体验收标准全部满足;没有未解释的失败测试;没有意外文件变更 | +| 验证命令 | `python manage.py check`; `pytest`; Playwright 对应命令 | +| Codex 执行提示 | 请执行文件汇总功能总体验收,运行后端全量测试和 Playwright 端到端测试,确认所有验收标准已满足。 | + +### FS-P7-004 合并回 V2 分支 + +| 项目 | 内容 | +| --- | --- | +| 任务类型 | Git / 收尾 | +| 前置任务 | FS-P7-003 | +| 涉及文件 | 无固定文件 | +| 目标 | 将开发分支合并回 `V2`,并在合并后再次运行总体验收 | +| 开发步骤 | 1. P7 通过后调用 `git-commit-summary` 提交阶段变更;2. 切换到 `V2`;3. 合并开发分支;4. 解决冲突但不得覆盖用户变更;5. 合并后运行总体验收;6. 保留最终 git status | +| 验收标准 | 开发分支成功合并到 `V2`;合并后测试通过;本地 Git 历史包含阶段提交 | +| 验证命令 | `git branch --show-current`; `git status --short`; `python manage.py check`; `pytest`; Playwright 对应命令 | +| Codex 执行提示 | 请在全部阶段完成后提交 P7 变更,切回 `V2` 并合并开发分支,合并后重新运行总体验收。 | + +### P7 阶段提交规则 + +| 项目 | 内容 | +| --- | --- | +| 阶段验证 | `python manage.py check`; `pytest`; Playwright 端到端测试 | +| 提交动作 | 调用 `git-commit-summary` 生成提交摘要并提交 | +| 合并动作 | 所有阶段提交完成后合并回 `V2` | +| Codex 执行提示 | P7 验证通过后,请调用 `git-commit-summary` 提交本阶段变更,然后合并回 `V2` 并再次总体验收。 | + +--- + +## 十三、测试分层要求 + +| 层级 | 验证内容 | 建议文件 | +| --- | --- | --- | +| 单元测试 | 触发词、附件版本、解压安全、文件扫描、页数统计、报告生成 | `tests/test_file_summary_*.py` | +| 接口测试 | 上传接口、批次状态接口、事件接口、下载接口、权限隔离 | `tests/test_file_summary_views.py` | +| 工作流集成测试 | 上传附件后发送提示词,完整执行到生成 Markdown/Excel | `tests/test_file_summary_workflow.py` | +| Playwright E2E | 登录、上传、触发、卡片更新、Markdown 渲染、下载、刷新恢复 | Playwright 测试文件 | +| 部署验证 | requirements 安装成功,Docker 中 7z/p7zip 可用,rar/7z 解压可跑通 | 部署说明和验证命令 | + +说明:测试样例文件不单独拆任务,可在测试代码中动态生成临时 pdf、docx、xlsx、pptx、zip、7z、rar、损坏文件或不可读文件。 + +--- + +## 十四、Codex 自动化执行规则 + +| 规则 | 内容 | +| --- | --- | +| 顺序执行 | 必须从 P0 到 P7 顺序执行,不得跳阶段 | +| 当前阶段优先 | 某阶段测试失败时,必须先修复当前阶段,不得继续后续阶段 | +| 连续失败处理 | 同一阶段连续 3 次失败时,记录阻塞原因、已尝试方案和下一步建议 | +| 每任务验证 | 每个任务完成后运行对应验证命令或说明无法运行原因 | +| 每阶段提交 | 每个阶段全部任务完成并验证通过后,调用 `git-commit-summary` 后本地提交 | +| 前端强验证 | P6 完成后必须运行 Playwright 端到端测试和截图/断言验证 | +| 不覆盖变更 | 不得回滚或覆盖用户已有未提交变更 | +| 合并收尾 | 全部完成后必须合并回 `V2` 并再次总体验收 | + +--- + +## 十五、推荐一键执行提示词 + +后续可直接对 Codex 输入: + +```text +请按 docs/开发计划/1.自动汇总.md 执行,从 V2 创建 codex/YYYYMMDD-自动汇总文件目录页数 分支,按 P0 到 P7 顺序开发、验证和阶段提交。每个阶段完成后调用 git-commit-summary 生成提交摘要并本地提交。全部完成后合并回 V2,并重新运行总体验收。 +``` + +--- + +## 十六、待执行前检查清单 + +| 检查项 | 状态 | +| --- | --- | +| 需求分析、功能设计、详细设计、数据库设计均已存在 | 待执行时确认 | +| 当前分支是否为 `V2` | 待执行时确认 | +| 是否存在用户未提交变更 | 待执行时确认 | +| Python 依赖是否可安装 | 待执行时确认 | +| Playwright 或对应 MCP/Skill 是否可用 | 待执行时确认 | +| 执行机器是否提供 `git-commit-summary` skill | 待执行时确认 | +| Docker 环境是否可安装 7z/p7zip | 待执行时确认 | diff --git a/docs/数据库设计/1.自动汇总.md b/docs/数据库设计/1.自动汇总.md new file mode 100644 index 0000000..194c506 --- /dev/null +++ b/docs/数据库设计/1.自动汇总.md @@ -0,0 +1,651 @@ +# 自动汇总文件夹文件目录与页数流程数据库设计 + +## 文档信息 + +| 项目 | 内容 | +| --- | --- | +| 需求分析文档 | docs/需求分析/1.自动汇总.md | +| 功能设计文档 | docs/功能设计/1.自动汇总.md | +| 详细设计文档 | docs/详细设计/1.自动汇总.md | +| 数据库类型 | SQLite / Django ORM | +| 表名前缀 | ra_ | +| 设计日期 | 2026-06-05 | +| 设计版本 | V1.0 | + +--- + +## 一、设计原则 + +| 原则 | 说明 | +| --- | --- | +| ORM 优先 | 当前项目使用 Django,实际落地以 Django Model 与 migration 为准 | +| SQLite 兼容 | 字段类型、索引和约束优先保证 SQLite 可运行 | +| 短表名前缀 | 使用 `ra_` 作为审核智能体文件汇总相关表前缀 | +| 不建枚举表 | 状态枚举使用 Django `TextChoices`,数据库存储字符串 | +| 对话隔离 | 所有附件、批次、导出文件均可追溯到 Conversation 和 User | +| 多版本附件 | 同一对话同名附件允许多次上传,以版本号区分 | +| 批次固化 | 每次汇总批次通过中间表绑定本次使用的附件版本,防止串文件 | +| 事件留痕 | 保留 WorkflowEvent,用于 SSE 断线续传、页面刷新恢复和排查问题 | + +--- + +## 二、ER 图 + +```mermaid +erDiagram + AUTH_USER ||--o{ CONVERSATION : owns + CONVERSATION ||--o{ MESSAGE : contains + CONVERSATION ||--o{ RA_FILE_ATTACHMENT : has + CONVERSATION ||--o{ RA_FILE_SUMMARY_BATCH : has + AUTH_USER ||--o{ RA_FILE_ATTACHMENT : uploads + AUTH_USER ||--o{ RA_FILE_SUMMARY_BATCH : runs + MESSAGE ||--o{ RA_FILE_SUMMARY_BATCH : triggers + RA_FILE_SUMMARY_BATCH ||--o{ RA_FILE_SUMMARY_BATCH_ATTACHMENT : binds + RA_FILE_ATTACHMENT ||--o{ RA_FILE_SUMMARY_BATCH_ATTACHMENT : selected + RA_FILE_SUMMARY_BATCH ||--o{ RA_FILE_SUMMARY_ITEM : produces + RA_FILE_SUMMARY_BATCH ||--o{ RA_WORKFLOW_NODE_RUN : tracks + RA_FILE_SUMMARY_BATCH ||--o{ RA_WORKFLOW_EVENT : emits + RA_FILE_SUMMARY_BATCH ||--o{ RA_EXPORTED_SUMMARY_FILE : exports +``` + +--- + +## 三、表结构设计 + +### 3.1 ra_file_attachment + +用户在对话右侧上传区上传后的附件记录。上传即存储,不代表已启动工作流。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键 | +| conversation_id | ForeignKey | bigint | 是 | 绑定对话 | +| user_id | ForeignKey | bigint | 是 | 上传用户 | +| original_name | CharField(255) | varchar(255) | 是 | 原始文件名 | +| version_no | PositiveIntegerField | integer | 是 | 同一对话同名文件版本号,从 1 递增 | +| is_active | BooleanField | bool | 是 | 是否当前默认版本 | +| storage_path | CharField(500) | varchar(500) | 是 | 文件存储路径 | +| file_size | BigIntegerField | bigint | 是 | 文件大小 | +| content_type | CharField(120) | varchar(120) | 否 | MIME 类型 | +| upload_status | CharField(20) | varchar(20) | 是 | uploaded、bound、deleted | +| created_at | DateTimeField | datetime | 是 | 上传时间 | + +唯一约束: + +| 约束名 | 字段 | +| --- | --- | +| uq_ra_attachment_conv_name_version | conversation_id, original_name, version_no | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_attachment_conv_created | conversation_id, created_at | 查询对话附件列表 | +| idx_ra_attachment_user_created | user_id, created_at | 查询用户上传记录 | +| idx_ra_attachment_active | conversation_id, original_name, is_active | 查询当前默认版本 | + +--- + +### 3.2 ra_file_summary_batch + +一次文件目录与页数汇总工作流批次。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键 | +| conversation_id | ForeignKey | bigint | 是 | 绑定对话 | +| user_id | ForeignKey | bigint | 是 | 执行用户 | +| trigger_message_id | ForeignKey | bigint | 否 | 触发工作流的用户消息 | +| batch_no | CharField(64) | varchar(64) | 是 | 批次编号,唯一 | +| product_name | CharField(200) | varchar(200) | 否 | 识别出的产品名称 | +| status | CharField(20) | varchar(20) | 是 | pending、running、success、failed | +| total_files | IntegerField | integer | 是 | 文件总数 | +| supported_files | IntegerField | integer | 是 | 支持统计文件数 | +| success_files | IntegerField | integer | 是 | 统计成功文件数 | +| failed_files | IntegerField | integer | 是 | 统计失败文件数 | +| unsupported_files | IntegerField | integer | 是 | 不支持文件数 | +| uncertain_files | IntegerField | integer | 是 | 页数不可确定文件数 | +| total_pages | IntegerField | integer | 是 | 总页数 | +| work_dir | CharField(500) | varchar(500) | 否 | 批次工作目录 | +| error_message | TextField | text | 否 | 批次异常说明 | +| created_at | DateTimeField | datetime | 是 | 创建时间 | +| started_at | DateTimeField | datetime | 否 | 开始时间 | +| finished_at | DateTimeField | datetime | 否 | 完成时间 | + +唯一约束: + +| 约束名 | 字段 | +| --- | --- | +| uq_ra_batch_no | batch_no | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_batch_conv_created | conversation_id, created_at | 查询对话下批次 | +| idx_ra_batch_user_created | user_id, created_at | 查询用户批次 | +| idx_ra_batch_status | status, created_at | 查询执行中或失败批次 | + +--- + +### 3.3 ra_file_summary_batch_attachment + +批次与附件版本绑定表。一个对话可多次上传同名附件,批次必须固化本次使用的附件版本。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键 | +| batch_id | ForeignKey | bigint | 是 | 汇总批次 | +| attachment_id | ForeignKey | bigint | 是 | 本次使用的附件版本 | +| source_role | CharField(20) | varchar(20) | 是 | archive、multi_file | +| created_at | DateTimeField | datetime | 是 | 绑定时间 | + +唯一约束: + +| 约束名 | 字段 | +| --- | --- | +| uq_ra_batch_attachment | batch_id, attachment_id | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_batch_attachment_batch | batch_id, created_at | 查询批次附件 | +| idx_ra_batch_attachment_attachment | attachment_id | 查询附件被哪些批次使用 | + +--- + +### 3.4 ra_file_summary_item + +文件明细表,记录扫描到的每个文件及页数统计结果。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键 | +| batch_id | ForeignKey | bigint | 是 | 所属批次 | +| file_index | PositiveIntegerField | integer | 是 | 文件序号 | +| directory_level | CharField(300) | varchar(300) | 否 | 目录层级 | +| file_name | CharField(255) | varchar(255) | 是 | 文件名 | +| file_type | CharField(20) | varchar(20) | 是 | 文件类型 | +| relative_path | CharField(500) | varchar(500) | 是 | 相对路径,用于展示和导出 | +| storage_path | CharField(500) | varchar(500) | 是 | 实际处理路径 | +| page_count | IntegerField | integer | 否 | 页数,失败或不可确定时为空 | +| statistics_status | CharField(20) | varchar(20) | 是 | success、failed、unsupported、uncertain、skipped | +| retry_count | PositiveIntegerField | integer | 是 | 页数统计重试次数 | +| error_message | TextField | text | 否 | 异常说明 | +| created_at | DateTimeField | datetime | 是 | 创建时间 | +| updated_at | DateTimeField | datetime | 是 | 更新时间 | + +唯一约束: + +| 约束名 | 字段 | +| --- | --- | +| uq_ra_item_batch_relative_path | batch_id, relative_path | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_item_batch_index | batch_id, file_index | 按序展示文件明细 | +| idx_ra_item_batch_status | batch_id, statistics_status | 查询失败/不可确定文件 | +| idx_ra_item_batch_type | batch_id, file_type | 按类型统计 | + +--- + +### 3.5 ra_workflow_node_run + +工作流节点运行状态表,用于右侧工作流卡片状态恢复。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键 | +| batch_id | ForeignKey | bigint | 是 | 所属批次 | +| node_code | CharField(40) | varchar(40) | 是 | 节点编码 | +| node_name | CharField(80) | varchar(80) | 是 | 节点名称 | +| status | CharField(20) | varchar(20) | 是 | pending、running、retrying、success、failed、skipped | +| progress | PositiveIntegerField | integer | 是 | 进度百分比,0-100 | +| message | TextField | text | 否 | 节点提示 | +| started_at | DateTimeField | datetime | 否 | 开始时间 | +| finished_at | DateTimeField | datetime | 否 | 完成时间 | + +唯一约束: + +| 约束名 | 字段 | +| --- | --- | +| uq_ra_node_batch_code | batch_id, node_code | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_node_batch_status | batch_id, status | 查询批次节点状态 | + +--- + +### 3.6 ra_workflow_event + +工作流事件表,用于 SSE 事件持久化、断线续传和调试。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键,同时可作为 event_id | +| batch_id | ForeignKey | bigint | 是 | 所属批次 | +| event_type | CharField(40) | varchar(40) | 是 | workflow_started、node_progress 等 | +| payload | JSONField | text/json | 是 | 事件载荷 | +| created_at | DateTimeField | datetime | 是 | 事件时间 | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_event_batch_id | batch_id, id | SSE after 续传 | +| idx_ra_event_batch_created | batch_id, created_at | 按时间查询事件 | + +--- + +### 3.7 ra_exported_summary_file + +导出文件记录表。下载链接运行时根据 export_id 生成。 + +| 字段名 | Django 类型 | SQLite 类型 | 必填 | 说明 | +| --- | --- | --- | --- | --- | +| id | BigAutoField | integer | 是 | 主键 | +| batch_id | ForeignKey | bigint | 是 | 所属批次 | +| export_type | CharField(20) | varchar(20) | 是 | markdown、excel | +| file_name | CharField(255) | varchar(255) | 是 | 导出文件名 | +| storage_path | CharField(500) | varchar(500) | 是 | 保存路径 | +| status | CharField(20) | varchar(20) | 是 | success、failed | +| error_message | TextField | text | 否 | 导出异常说明 | +| created_at | DateTimeField | datetime | 是 | 生成时间 | + +索引: + +| 索引名 | 字段 | 说明 | +| --- | --- | --- | +| idx_ra_export_batch_type | batch_id, export_type | 查询批次导出文件 | +| idx_ra_export_batch_created | batch_id, created_at | 按生成时间查询 | + +--- + +## 四、枚举设计 + +本功能不建立枚举表,枚举通过 Django `TextChoices` 定义,数据库存储字符串。 + +### 4.1 附件状态 upload_status + +| 值 | 中文 | 说明 | +| --- | --- | --- | +| uploaded | 已上传 | 上传完成,尚未绑定批次 | +| bound | 已绑定 | 已被某个批次使用 | +| deleted | 已删除 | 用户逻辑删除,不再作为默认候选 | + +### 4.2 批次状态 batch.status + +| 值 | 中文 | 说明 | +| --- | --- | --- | +| pending | 待执行 | 批次已创建 | +| running | 执行中 | 后台工作流运行中 | +| success | 成功 | 工作流完成 | +| failed | 失败 | 批次级失败 | + +### 4.3 节点状态 node.status + +| 值 | 中文 | 说明 | +| --- | --- | --- | +| pending | 等待中 | 节点未开始 | +| running | 执行中 | 节点正在执行 | +| retrying | 重试中 | 单文件解析失败后重试 | +| success | 成功 | 节点执行成功 | +| failed | 失败 | 节点失败 | +| skipped | 跳过 | 当前批次不需要执行该节点 | + +### 4.4 文件统计状态 statistics_status + +| 值 | 中文 | 说明 | +| --- | --- | --- | +| success | 成功 | 页数统计成功 | +| failed | 失败 | 重试后仍失败 | +| unsupported | 不支持 | 文件类型不在支持范围 | +| uncertain | 不确定 | 文件可读,但无可靠页数元数据 | +| skipped | 跳过 | 空文件、隐藏文件或规则跳过 | + +### 4.5 导出类型 export_type + +| 值 | 中文 | 说明 | +| --- | --- | --- | +| markdown | Markdown | Markdown 汇总报告 | +| excel | Excel | Excel 明细文件 | + +### 4.6 导出状态 export.status + +| 值 | 中文 | 说明 | +| --- | --- | --- | +| success | 成功 | 导出文件生成成功 | +| failed | 失败 | 导出失败 | + +--- + +## 五、关系与业务规则 + +### 5.1 对话与附件 + +```text +Conversation 1:N ra_file_attachment +``` + +规则: + +| 规则 | 说明 | +| --- | --- | +| 上传即存储 | 用户上传后立即创建 FileAttachment | +| 对话隔离 | 附件只能被同一 Conversation 下的批次使用 | +| 多版本 | 同一 conversation + original_name 可存在多个 version_no | +| 默认版本 | is_active=true 的记录作为默认候选版本 | +| 逻辑删除 | 删除附件时设置 upload_status=deleted,不立即物理删除 | + +### 5.2 对话与批次 + +```text +Conversation 1:N ra_file_summary_batch +``` + +规则: + +| 规则 | 说明 | +| --- | --- | +| 多次汇总 | 同一对话允许多次触发自动汇总 | +| 提示词触发 | 批次由用户消息触发,可关联 trigger_message_id | +| 批次固化 | 批次启动时固化本次使用的附件版本 | + +### 5.3 批次与附件版本 + +```text +ra_file_summary_batch N:M ra_file_attachment +``` + +通过 `ra_file_summary_batch_attachment` 实现。 + +规则: + +| 规则 | 说明 | +| --- | --- | +| 不串文件 | 工作流只能读取中间表绑定的附件 | +| 保留历史 | 即使附件后续上传新版本,历史批次仍指向旧版本 | +| 版本选择 | 用户未选择时默认使用同名文件的最新 active 版本 | + +### 5.4 批次与文件明细 + +```text +ra_file_summary_batch 1:N ra_file_summary_item +``` + +规则: + +| 规则 | 说明 | +| --- | --- | +| 相对路径唯一 | 同一批次下 relative_path 唯一 | +| 处理路径保留 | relative_path 用于展示,storage_path 用于后台处理 | +| 单文件失败不阻断 | 文件解析失败记录 failed,批次继续处理其他文件 | + +--- + +## 六、索引设计汇总 + +| 表 | 索引/约束 | 字段 | 用途 | +| --- | --- | --- | --- | +| ra_file_attachment | uq_ra_attachment_conv_name_version | conversation_id, original_name, version_no | 同名附件版本唯一 | +| ra_file_attachment | idx_ra_attachment_conv_created | conversation_id, created_at | 对话附件列表 | +| ra_file_attachment | idx_ra_attachment_user_created | user_id, created_at | 用户上传记录 | +| ra_file_attachment | idx_ra_attachment_active | conversation_id, original_name, is_active | 默认版本查询 | +| ra_file_summary_batch | uq_ra_batch_no | batch_no | 批次编号唯一 | +| ra_file_summary_batch | idx_ra_batch_conv_created | conversation_id, created_at | 对话批次列表 | +| ra_file_summary_batch | idx_ra_batch_user_created | user_id, created_at | 用户批次列表 | +| ra_file_summary_batch | idx_ra_batch_status | status, created_at | 查询运行中/失败批次 | +| ra_file_summary_batch_attachment | uq_ra_batch_attachment | batch_id, attachment_id | 批次附件唯一 | +| ra_file_summary_item | uq_ra_item_batch_relative_path | batch_id, relative_path | 批次内文件唯一 | +| ra_file_summary_item | idx_ra_item_batch_index | batch_id, file_index | 文件明细排序 | +| ra_file_summary_item | idx_ra_item_batch_status | batch_id, statistics_status | 查询异常文件 | +| ra_workflow_node_run | uq_ra_node_batch_code | batch_id, node_code | 每批次每节点唯一 | +| ra_workflow_event | idx_ra_event_batch_id | batch_id, id | SSE 断点续传 | +| ra_exported_summary_file | idx_ra_export_batch_type | batch_id, export_type | 查询导出文件 | + +--- + +## 七、SQLite 参考 DDL + +> 说明:以下 DDL 为设计参考,实际落地以 Django migration 为准。 + +```sql +CREATE TABLE ra_file_attachment ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + conversation_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + original_name VARCHAR(255) NOT NULL, + version_no INTEGER NOT NULL DEFAULT 1, + is_active BOOLEAN NOT NULL DEFAULT 1, + storage_path VARCHAR(500) NOT NULL, + file_size BIGINT NOT NULL DEFAULT 0, + content_type VARCHAR(120) NOT NULL DEFAULT '', + upload_status VARCHAR(20) NOT NULL DEFAULT 'uploaded', + created_at DATETIME NOT NULL, + UNIQUE (conversation_id, original_name, version_no) +); + +CREATE INDEX idx_ra_attachment_conv_created +ON ra_file_attachment (conversation_id, created_at); + +CREATE INDEX idx_ra_attachment_user_created +ON ra_file_attachment (user_id, created_at); + +CREATE INDEX idx_ra_attachment_active +ON ra_file_attachment (conversation_id, original_name, is_active); +``` + +```sql +CREATE TABLE ra_file_summary_batch ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + conversation_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + trigger_message_id BIGINT NULL, + batch_no VARCHAR(64) NOT NULL UNIQUE, + product_name VARCHAR(200) NOT NULL DEFAULT '', + status VARCHAR(20) NOT NULL DEFAULT 'pending', + total_files INTEGER NOT NULL DEFAULT 0, + supported_files INTEGER NOT NULL DEFAULT 0, + success_files INTEGER NOT NULL DEFAULT 0, + failed_files INTEGER NOT NULL DEFAULT 0, + unsupported_files INTEGER NOT NULL DEFAULT 0, + uncertain_files INTEGER NOT NULL DEFAULT 0, + total_pages INTEGER NOT NULL DEFAULT 0, + work_dir VARCHAR(500) NOT NULL DEFAULT '', + error_message TEXT NOT NULL DEFAULT '', + created_at DATETIME NOT NULL, + started_at DATETIME NULL, + finished_at DATETIME NULL +); + +CREATE INDEX idx_ra_batch_conv_created +ON ra_file_summary_batch (conversation_id, created_at); + +CREATE INDEX idx_ra_batch_user_created +ON ra_file_summary_batch (user_id, created_at); + +CREATE INDEX idx_ra_batch_status +ON ra_file_summary_batch (status, created_at); +``` + +```sql +CREATE TABLE ra_file_summary_batch_attachment ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch_id BIGINT NOT NULL, + attachment_id BIGINT NOT NULL, + source_role VARCHAR(20) NOT NULL DEFAULT 'multi_file', + created_at DATETIME NOT NULL, + UNIQUE (batch_id, attachment_id) +); + +CREATE INDEX idx_ra_batch_attachment_batch +ON ra_file_summary_batch_attachment (batch_id, created_at); + +CREATE INDEX idx_ra_batch_attachment_attachment +ON ra_file_summary_batch_attachment (attachment_id); +``` + +```sql +CREATE TABLE ra_file_summary_item ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch_id BIGINT NOT NULL, + file_index INTEGER NOT NULL, + directory_level VARCHAR(300) NOT NULL DEFAULT '', + file_name VARCHAR(255) NOT NULL, + file_type VARCHAR(20) NOT NULL, + relative_path VARCHAR(500) NOT NULL, + storage_path VARCHAR(500) NOT NULL, + page_count INTEGER NULL, + statistics_status VARCHAR(20) NOT NULL DEFAULT 'skipped', + retry_count INTEGER NOT NULL DEFAULT 0, + error_message TEXT NOT NULL DEFAULT '', + created_at DATETIME NOT NULL, + updated_at DATETIME NOT NULL, + UNIQUE (batch_id, relative_path) +); + +CREATE INDEX idx_ra_item_batch_index +ON ra_file_summary_item (batch_id, file_index); + +CREATE INDEX idx_ra_item_batch_status +ON ra_file_summary_item (batch_id, statistics_status); + +CREATE INDEX idx_ra_item_batch_type +ON ra_file_summary_item (batch_id, file_type); +``` + +```sql +CREATE TABLE ra_workflow_node_run ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch_id BIGINT NOT NULL, + node_code VARCHAR(40) NOT NULL, + node_name VARCHAR(80) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + progress INTEGER NOT NULL DEFAULT 0, + message TEXT NOT NULL DEFAULT '', + started_at DATETIME NULL, + finished_at DATETIME NULL, + UNIQUE (batch_id, node_code) +); + +CREATE INDEX idx_ra_node_batch_status +ON ra_workflow_node_run (batch_id, status); +``` + +```sql +CREATE TABLE ra_workflow_event ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch_id BIGINT NOT NULL, + event_type VARCHAR(40) NOT NULL, + payload TEXT NOT NULL DEFAULT '{}', + created_at DATETIME NOT NULL +); + +CREATE INDEX idx_ra_event_batch_id +ON ra_workflow_event (batch_id, id); + +CREATE INDEX idx_ra_event_batch_created +ON ra_workflow_event (batch_id, created_at); +``` + +```sql +CREATE TABLE ra_exported_summary_file ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + batch_id BIGINT NOT NULL, + export_type VARCHAR(20) NOT NULL, + file_name VARCHAR(255) NOT NULL, + storage_path VARCHAR(500) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'success', + error_message TEXT NOT NULL DEFAULT '', + created_at DATETIME NOT NULL +); + +CREATE INDEX idx_ra_export_batch_type +ON ra_exported_summary_file (batch_id, export_type); + +CREATE INDEX idx_ra_export_batch_created +ON ra_exported_summary_file (batch_id, created_at); +``` + +--- + +## 八、Django ORM 落地注意事项 + +### 8.1 db_table + +每个模型通过 `class Meta: db_table = "ra_xxx"` 固定表名,避免 Django 默认生成较长表名。 + +### 8.2 JSONField + +`WorkflowEvent.payload` 使用 Django `models.JSONField(default=dict)`。SQLite 下实际以文本形式存储,Django 负责序列化与反序列化。 + +### 8.3 版本号生成 + +同一对话同名文件上传时: + +```text +version_no = max(existing version_no) + 1 +``` + +若新版本设为默认版本,需要将旧版本 `is_active` 更新为 false。 + +### 8.4 逻辑删除 + +附件删除时: + +```text +upload_status = deleted +is_active = false +``` + +历史批次仍可通过中间表追溯该附件。 + +### 8.5 批次选择附件 + +用户发送提示词触发工作流时: + +| 场景 | 处理 | +| --- | --- | +| 用户显式选择附件版本 | 使用所选 attachment_id | +| 用户未选择版本 | 使用当前对话下 is_active=true 且未删除的附件 | +| 存在多个同名 active 异常 | 取 created_at 最新,并记录待修复数据异常 | + +--- + +## 九、数据保留策略 + +| 数据 | Demo 策略 | 正式部署建议 | +| --- | --- | --- | +| 上传附件记录 | 永久保留 | 随会话归档周期清理 | +| 上传原始文件 | 永久保留 | 可按用户/项目配置保留期限 | +| 汇总批次 | 永久保留 | 保留用于审计追溯 | +| 文件明细 | 永久保留 | 保留用于历史报告复现 | +| 工作流事件 | 永久保留 | 可定期清理已完成批次的事件 | +| 导出文件 | 永久保留 | 可设置下载有效期或归档 | + +--- + +## 十、待确认事项 + +| 序号 | 问题 | 当前设计 | 状态 | +| --- | --- | --- | --- | +| 1 | 正式部署是否从 SQLite 迁移到 PostgreSQL/MySQL | 当前按 SQLite/Django ORM 设计,保留 ORM 兼容性 | 待后续确认 | +| 2 | 同名附件 active 是否允许多个 | 设计上不允许,代码更新时应关闭旧 active | 待开发实现 | +| 3 | 文件物理删除时机 | Demo 不物理删除 | 待后续确认 | + +--- + +## 十一、开发顺序建议 + +1. 在 `review_agent/models.py` 中新增上述 7 个模型。 +2. 为状态字段定义 Django `TextChoices`。 +3. 配置 `db_table`、`indexes`、`constraints`。 +4. 执行 `python manage.py makemigrations review_agent` 生成迁移。 +5. 执行 `python manage.py migrate` 验证 SQLite 可落表。 +6. 编写模型级测试,覆盖同名附件版本、批次附件绑定、唯一约束和权限查询。 diff --git a/docs/详细设计/1.自动汇总.md b/docs/详细设计/1.自动汇总.md new file mode 100644 index 0000000..36f468a --- /dev/null +++ b/docs/详细设计/1.自动汇总.md @@ -0,0 +1,930 @@ +# 自动汇总文件夹文件目录与页数流程详细设计 + +## 文档信息 + +| 项目 | 内容 | +| --- | --- | +| 需求分析文档 | docs/需求分析/1.自动汇总.md | +| 功能设计文档 | docs/功能设计/1.自动汇总.md | +| 功能名称 | 自动汇总文件夹文件目录与页数 | +| 所属模块 | 审核智能体 review_agent | +| 设计日期 | 2026-06-05 | +| 设计版本 | V1.0 | + +--- + +## 一、详细设计目标 + +本详细设计用于指导“自动汇总文件夹文件目录与页数”功能开发落地,覆盖代码目录、数据模型、接口契约、后台工作流、Skill 拆分、轻量依赖、前端三栏布局、SSE 实时状态、异常重试和测试用例。 + +核心约束: + +| 约束 | 说明 | +| --- | --- | +| 对话绑定 | 上传文件与当前 Conversation 绑定,一个对话对应一套文件,不能串文件 | +| 上传即存储 | 用户拖拽或选择文件后立即保存,但不启动工作流 | +| 提示词触发 | 用户发送消息后,根据提示词判断是否启动自动汇总工作流 | +| 后台异步 | 工作流后台执行,右侧第三栏工作流卡片实时更新 | +| 轻量依赖 | 优先使用 Python 内部库和轻量第三方库,不强依赖 LibreOffice | +| 老格式支持 | doc、xls、ppt 进入处理流程,能读到页数则统计,读不到则记录异常 | +| 结果存档 | 批次、文件、节点、事件、明细、导出文件全部入库 | + +--- + +## 二、代码结构设计 + +### 2.1 目录结构 + +在现有 `review_agent` 应用内按模块重新划分文件处理能力。Django 模型仍集中放在 `review_agent/models.py`,其余代码放入 `review_agent/file_summary/`。 + +```text +review_agent/ + models.py + urls.py + views.py + services.py + file_summary/ + __init__.py + constants.py + schemas.py + storage.py + workflow.py + events.py + urls.py + views.py + services/ + __init__.py + archive.py + inventory.py + page_count.py + product_detect.py + report.py + export_excel.py + workflow_trigger.py + skills/ + __init__.py + base.py + registry.py + upload_intake.py + archive_extract.py + file_inventory.py + document_page_count.py + product_detect.py + summary_report.py + excel_export.py +``` + +### 2.2 文件职责 + +| 文件 | 职责 | +| --- | --- | +| review_agent/models.py | 集中定义 Conversation、Message、文件汇总相关模型 | +| file_summary/constants.py | 状态、节点、文件类型、事件类型常量 | +| file_summary/schemas.py | dataclass 入参出参结构,避免业务层直接传散乱 dict | +| file_summary/storage.py | 上传文件、工作目录、导出文件路径生成与保存 | +| file_summary/workflow.py | WorkflowExecutor,串行执行节点图 | +| file_summary/events.py | 工作流事件持久化与 SSE 格式化 | +| file_summary/views.py | 上传暂存、启动工作流、状态查询、SSE、下载接口 | +| services/archive.py | 压缩包识别、zip/7z/rar 解压 | +| services/inventory.py | 文件遍历与清单生成 | +| services/page_count.py | 文件页数统计与 3 次重试 | +| services/product_detect.py | 产品名识别 | +| services/report.py | Markdown 报告和对话简表生成 | +| services/export_excel.py | Excel 文件导出 | +| services/workflow_trigger.py | 根据提示词判断是否触发自动汇总工作流 | +| skills/base.py | Skill 基类与统一返回结构 | +| skills/registry.py | Skill 注册与按需加载 | +| skills/*.py | 各工作流节点对应 Skill | + +--- + +## 三、依赖设计 + +### 3.1 requirements 建议 + +```text +Django==5.2.14 +pypdf +python-docx +python-pptx +openpyxl +xlrd +olefile +py7zr +``` + +### 3.2 格式处理策略 + +| 格式 | 处理库 | 统计口径 | 失败策略 | +| --- | --- | --- | --- | +| pdf | pypdf | PDF 页面数 | 重试 3 次,仍失败记录异常 | +| docx | python-docx | 优先读取内置页数属性 | 读不到记录“页数不可确定” | +| doc | olefile | 读取 OLE 元数据页数 | 读不到记录“页数不可确定” | +| pptx | python-pptx | 幻灯片数量 | 重试 3 次,仍失败记录异常 | +| ppt | olefile | 读取 OLE 元数据页数/幻灯片数 | 读不到记录“页数不可确定” | +| xlsx | openpyxl | 工作表数量 | 重试 3 次,仍失败记录异常 | +| xls | xlrd | 工作表数量 | 重试 3 次,仍失败记录异常 | + +### 3.3 压缩包处理策略 + +| 格式 | 处理方式 | 说明 | +| --- | --- | --- | +| zip | Python 标准库 zipfile | 必须支持 | +| 7z | py7zr | 必须支持 | +| rar | 优先系统 7z 命令 | Docker 镜像需安装 7-Zip/p7zip | + +### 3.4 Docker 部署说明 + +Demo 运行不强依赖 LibreOffice。若未来要求 doc/docx/ppt/pptx 页数与 Office 打开后的分页完全一致,可在 Docker 镜像中额外安装 LibreOffice headless,再通过“转换 PDF 后统计页数”的增强策略实现。 + +RAR 解压如需稳定支持,Docker 镜像需要安装 7-Zip/p7zip,并确保 `7z` 命令在 PATH 中可调用。 + +--- + +## 四、数据模型详细设计 + +模型集中放在 `review_agent/models.py`,按“会话模型”和“文件汇总模型”分段。 + +### 4.1 FileAttachment + +用户上传即存储的文件记录。此时尚未启动工作流。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| conversation | ForeignKey(Conversation) | CASCADE, db_index | 绑定对话 | +| user | ForeignKey(User) | CASCADE, db_index | 上传用户 | +| original_name | CharField(255) | required | 原始文件名 | +| storage_path | CharField(500) | required | 本地保存路径 | +| file_size | BigIntegerField | default=0 | 文件大小 | +| content_type | CharField(120) | blank | MIME 类型 | +| upload_status | CharField(20) | choices | uploaded、bound、deleted | +| created_at | DateTimeField | auto_now_add | 上传时间 | + +索引: + +```text +(conversation, created_at) +(user, created_at) +``` + +### 4.2 FileSummaryBatch + +一次自动汇总工作流批次。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| conversation | ForeignKey(Conversation) | CASCADE, db_index | 绑定对话 | +| user | ForeignKey(User) | CASCADE, db_index | 执行用户 | +| trigger_message | ForeignKey(Message) | SET_NULL, null | 触发工作流的用户消息 | +| batch_no | CharField(64) | unique | 批次编号 | +| product_name | CharField(200) | blank | 产品名称 | +| status | CharField(20) | choices | pending、running、success、failed | +| total_files | IntegerField | default=0 | 文件总数 | +| supported_files | IntegerField | default=0 | 支持统计数 | +| success_files | IntegerField | default=0 | 成功数 | +| failed_files | IntegerField | default=0 | 失败数 | +| unsupported_files | IntegerField | default=0 | 不支持数 | +| uncertain_files | IntegerField | default=0 | 页数不可确定数 | +| total_pages | IntegerField | default=0 | 总页数 | +| work_dir | CharField(500) | blank | 工作目录 | +| error_message | TextField | blank | 批次错误 | +| created_at | DateTimeField | auto_now_add | 创建时间 | +| started_at | DateTimeField | null | 开始时间 | +| finished_at | DateTimeField | null | 结束时间 | + +### 4.3 FileSummaryBatchAttachment + +批次与上传文件的绑定表,确保工作流只读取本批次文件。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| batch | ForeignKey(FileSummaryBatch) | CASCADE | 批次 | +| attachment | ForeignKey(FileAttachment) | CASCADE | 上传文件 | +| created_at | DateTimeField | auto_now_add | 绑定时间 | + +唯一约束: + +```text +unique(batch, attachment) +``` + +### 4.4 FileSummaryItem + +文件明细记录。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 所属批次 | +| file_index | IntegerField | required | 文件序号 | +| directory_level | CharField(300) | blank | 目录层级 | +| file_name | CharField(255) | required | 文件名 | +| file_type | CharField(20) | required | 扩展名 | +| relative_path | CharField(500) | required | 相对路径 | +| storage_path | CharField(500) | required | 实际处理路径 | +| page_count | IntegerField | null | 页数 | +| statistics_status | CharField(20) | choices | success、failed、unsupported、uncertain、skipped | +| retry_count | IntegerField | default=0 | 重试次数 | +| error_message | TextField | blank | 异常说明 | +| created_at | DateTimeField | auto_now_add | 创建时间 | +| updated_at | DateTimeField | auto_now | 更新时间 | + +唯一约束: + +```text +unique(batch, relative_path) +``` + +### 4.5 WorkflowNodeRun + +工作流节点状态记录。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 批次 | +| node_code | CharField(40) | required | 节点编码 | +| node_name | CharField(80) | required | 节点名称 | +| status | CharField(20) | choices | pending、running、retrying、success、failed、skipped | +| progress | IntegerField | default=0 | 进度百分比 | +| message | TextField | blank | 节点说明 | +| started_at | DateTimeField | null | 开始时间 | +| finished_at | DateTimeField | null | 完成时间 | + +唯一约束: + +```text +unique(batch, node_code) +``` + +### 4.6 WorkflowEvent + +SSE 事件持久化记录,用于页面刷新后恢复和调试。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 批次 | +| event_type | CharField(40) | required | 事件类型 | +| payload | JSONField | default=dict | 事件载荷 | +| created_at | DateTimeField | auto_now_add | 创建时间 | + +### 4.7 ExportedSummaryFile + +导出文件记录。 + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| id | BigAutoField | PK | 主键 | +| batch | ForeignKey(FileSummaryBatch) | CASCADE, db_index | 批次 | +| export_type | CharField(20) | choices | markdown、excel | +| file_name | CharField(255) | required | 文件名 | +| storage_path | CharField(500) | required | 保存路径 | +| status | CharField(20) | choices | success、failed | +| error_message | TextField | blank | 异常 | +| created_at | DateTimeField | auto_now_add | 生成时间 | + +下载链接运行时根据 `export_id` 生成,不建议长期存储静态 URL。 + +--- + +## 五、常量与状态设计 + +### 5.1 支持格式 + +```python +SUPPORTED_PAGE_TYPES = {"pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx"} +ARCHIVE_TYPES = {"zip", "7z", "rar"} +``` + +### 5.2 工作流节点 + +```python +WORKFLOW_NODES = [ + ("upload", "上传中"), + ("extract", "解压中"), + ("inventory", "扫描中"), + ("page_count", "解析页数中"), + ("product_detect", "识别产品名中"), + ("report", "输出 Markdown 中"), + ("excel_export", "输出 Excel 中"), + ("completed", "已完成"), +] +``` + +### 5.3 触发词规则 + +`workflow_trigger.py` 先用规则判断,后续可升级为 LLM 意图识别。 + +```python +SUMMARY_TRIGGER_KEYWORDS = [ + "自动汇总", + "文件目录", + "页数", + "统计文件", + "汇总目录", + "目录与页数", +] +``` + +规则: + +| 条件 | 结果 | +| --- | --- | +| 当前对话存在未绑定或最近上传文件,且提示词命中关键词 | 启动自动汇总工作流 | +| 未命中关键词 | 走普通 LLM 对话 | +| 命中关键词但没有上传文件 | AI 回复提示“请先上传文件或压缩包” | + +--- + +## 六、服务与方法签名 + +### 6.1 storage.py + +```python +def save_attachment(conversation, user, uploaded_file) -> FileAttachment: + """保存上传文件并绑定当前对话。""" + +def build_batch_work_dir(batch: FileSummaryBatch) -> Path: + """生成批次工作目录。""" + +def build_export_path(batch: FileSummaryBatch, suffix: str) -> Path: + """生成导出文件路径。""" +``` + +存储目录: + +```text +media/review_agent/ + user_{user_id}/ + conversation_{conversation_id}/ + attachments/ + batches/ + batch_{batch_id}/ + input/ + extracted/ + exports/ +``` + +### 6.2 archive.py + +```python +def is_archive(path: Path) -> bool: + """判断是否压缩包。""" + +def extract_archive(source: Path, target_dir: Path) -> list[Path]: + """解压 zip、7z、rar,返回解压后的文件路径列表。""" + +def extract_zip(source: Path, target_dir: Path) -> list[Path]: + """使用 zipfile 解压。""" + +def extract_7z(source: Path, target_dir: Path) -> list[Path]: + """使用 py7zr 解压。""" + +def extract_rar(source: Path, target_dir: Path) -> list[Path]: + """优先调用系统 7z 命令解压 rar。""" +``` + +安全规则: + +| 规则 | 说明 | +| --- | --- | +| 路径穿越检查 | 解压后的最终路径必须仍在 target_dir 内 | +| 文件名清理 | 保留原名,但禁止绝对路径和上级目录跳转 | +| 解压失败 | 抛出 ArchiveExtractError,批次失败 | + +### 6.3 inventory.py + +```python +def scan_files(batch: FileSummaryBatch, roots: list[Path]) -> list[FileSummaryItem]: + """扫描目录或散装文件,创建 FileSummaryItem。""" + +def build_directory_level(relative_path: Path) -> str: + """根据相对路径生成目录层级。""" + +def normalize_file_type(path: Path) -> str: + """返回小写扩展名,不含点。""" +``` + +### 6.4 page_count.py + +```python +def count_pages(item: FileSummaryItem) -> PageCountResult: + """根据文件类型分发页数统计。""" + +def count_pages_with_retry(item: FileSummaryItem, max_retry: int = 3) -> PageCountResult: + """失败最多重试 3 次。""" + +def count_pdf(path: Path) -> int: + """使用 pypdf 统计 PDF 页数。""" + +def count_docx(path: Path) -> PageCountResult: + """使用 python-docx 读取内置页数属性。""" + +def count_doc(path: Path) -> PageCountResult: + """使用 olefile 读取老 doc 的 OLE 元数据页数。""" + +def count_xlsx(path: Path) -> int: + """使用 openpyxl 统计工作表数量。""" + +def count_xls(path: Path) -> int: + """使用 xlrd 统计工作表数量。""" + +def count_pptx(path: Path) -> int: + """使用 python-pptx 统计幻灯片数量。""" + +def count_ppt(path: Path) -> PageCountResult: + """使用 olefile 读取老 ppt 的 OLE 元数据页数或幻灯片数。""" +``` + +`PageCountResult`: + +```python +@dataclass +class PageCountResult: + status: str + page_count: int | None = None + error_message: str = "" +``` + +状态规则: + +| 情况 | status | page_count | +| --- | --- | --- | +| 成功读取页数 | success | 整数 | +| 不支持类型 | unsupported | None | +| 文件可读但页数无元数据 | uncertain | None | +| 解析异常且重试失败 | failed | None | + +### 6.5 product_detect.py + +```python +def detect_product_name(batch: FileSummaryBatch) -> ProductDetectResult: + """从目录名、文件名和少量元数据中识别产品名。""" + +def update_conversation_title(batch: FileSummaryBatch, product_name: str) -> None: + """按规则更新对话标题。""" +``` + +产品名识别优先级: + +| 优先级 | 来源 | +| --- | --- | +| 1 | 顶层目录名 | +| 2 | 文件名中包含“产品”“试剂盒”“说明书”等关键词的片段 | +| 3 | docx 文档属性 title | +| 4 | PDF 元数据 title | + +### 6.6 report.py + +```python +def build_summary_stats(batch: FileSummaryBatch) -> dict: + """汇总统计数据。""" + +def build_chat_markdown(batch: FileSummaryBatch) -> str: + """生成对话框展示 Markdown 简表。""" + +def build_full_markdown_report(batch: FileSummaryBatch) -> str: + """生成完整 Markdown 报告。""" + +def save_markdown_report(batch: FileSummaryBatch) -> ExportedSummaryFile: + """保存 Markdown 报告并创建导出记录。""" +``` + +### 6.7 export_excel.py + +```python +def build_excel_workbook(batch: FileSummaryBatch) -> Workbook: + """构建 Excel Workbook。""" + +def save_excel(batch: FileSummaryBatch) -> ExportedSummaryFile: + """保存 Excel 并创建导出记录。""" +``` + +工作表: + +| Sheet | 字段 | +| --- | --- | +| 汇总信息 | 批次编号、产品名、文件总数、成功数、失败数、不可确定数、总页数 | +| 文件明细 | 序号、目录层级、文件名、类型、页数、相对路径、状态、重试次数、异常说明 | + +--- + +## 七、Skill 详细设计 + +### 7.1 BaseSkill + +```python +class BaseSkill: + name: str + node_code: str + + def run(self, context: WorkflowContext) -> SkillResult: + raise NotImplementedError +``` + +`WorkflowContext`: + +```python +@dataclass +class WorkflowContext: + batch_id: int + conversation_id: int + user_id: int + message_id: int | None = None +``` + +`SkillResult`: + +```python +@dataclass +class SkillResult: + success: bool + message: str = "" + data: dict = field(default_factory=dict) +``` + +### 7.2 Skill 列表 + +| Skill 类名 | 节点 | 调用服务 | +| --- | --- | --- | +| UploadIntakeSkill | upload | storage.py | +| ArchiveExtractSkill | extract | archive.py | +| FileInventorySkill | inventory | inventory.py | +| DocumentPageCountSkill | page_count | page_count.py | +| ProductDetectSkill | product_detect | product_detect.py | +| SummaryReportSkill | report | report.py | +| ExcelExportSkill | excel_export | export_excel.py | + +--- + +## 八、工作流执行器详细设计 + +### 8.1 执行入口 + +```python +def start_file_summary_workflow(batch_id: int) -> None: + thread = threading.Thread( + target=WorkflowExecutor().run, + args=(batch_id,), + daemon=True, + ) + thread.start() +``` + +### 8.2 执行伪代码 + +```python +class WorkflowExecutor: + def run(self, batch_id: int) -> None: + batch = FileSummaryBatch.objects.get(pk=batch_id) + self.mark_batch_running(batch) + self.emit("workflow_started", batch, {"batch_id": batch.id}) + + try: + for node_code in self.resolve_nodes(batch): + self.run_node(batch, node_code) + self.mark_batch_success(batch) + self.emit("workflow_completed", batch, self.build_completed_payload(batch)) + except Exception as exc: + self.mark_batch_failed(batch, str(exc)) + self.emit("workflow_failed", batch, {"message": str(exc)}) +``` + +### 8.3 节点跳过规则 + +| 节点 | 跳过条件 | +| --- | --- | +| extract | 当前批次没有压缩包 | +| product_detect | 没有任何可用于识别的文件名、目录名或元数据 | + +--- + +## 九、接口详细设计 + +### 9.1 上传暂存接口 + +```text +POST /api/review-agent/conversations/{conversation_id}/attachments/ +Content-Type: multipart/form-data +``` + +请求: + +| 参数 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| files[] | File[] | 是 | 一个或多个文件 | + +响应: + +```json +{ + "attachments": [ + { + "id": 101, + "original_name": "注册资料.zip", + "file_size": 204800, + "upload_status": "uploaded" + } + ] +} +``` + +权限: + +```text +conversation.user 必须等于 request.user +``` + +### 9.2 发送消息并按需触发工作流 + +沿用现有 `POST /chat/stream/` SSE 能力,在 `stream_chat` 中增加判断: + +```text +用户发送 prompt +-> 保存 Message +-> 判断 prompt 是否命中自动汇总工作流 +-> 命中则创建 FileSummaryBatch 并启动后台工作流 +-> SSE 返回 workflow_meta +-> 未命中则走原 LLM 流式回复 +``` + +新增 SSE meta: + +```json +{ + "conversation_id": 1, + "title": "新对话", + "workflow": { + "type": "file_summary", + "batch_id": 12, + "status": "running" + } +} +``` + +### 9.3 查询批次状态 + +```text +GET /api/review-agent/file-summary/{batch_id}/ +``` + +响应: + +```json +{ + "batch": { + "id": 12, + "batch_no": "FS202606050001", + "status": "running", + "product_name": "", + "total_files": 24, + "success_files": 10, + "failed_files": 1, + "uncertain_files": 2, + "total_pages": 180 + }, + "nodes": [ + { + "node_code": "page_count", + "node_name": "解析页数中", + "status": "running", + "progress": 45, + "message": "正在解析 11/24" + } + ], + "exports": [] +} +``` + +### 9.4 工作流事件流 + +```text +GET /api/review-agent/file-summary/{batch_id}/events/?after={event_id} +``` + +响应类型:`text/event-stream` + +事件: + +```text +event: node_progress +data: {"event_id": 301, "batch_id": 12, "node_code": "page_count", "status": "running", "progress": 45, "message": "正在解析 11/24"} +``` + +### 9.5 下载导出文件 + +```text +GET /api/review-agent/file-summary/exports/{export_id}/download/ +``` + +权限: + +```text +ExportedSummaryFile -> batch -> conversation -> user 必须为当前用户 +``` + +--- + +## 十、前端详细设计 + +### 10.1 三栏布局 + +页面调整为三栏: + +| 区域 | 内容 | +| --- | --- | +| 左侧栏 | 对话历史 | +| 中间栏 | 聊天消息、输入框 | +| 右侧栏上半部分 | 拖拽式文件导入区 | +| 右侧栏下半部分 | 工作流卡片列表 | + +HTML 结构建议: + +```html +
+ +
+ +
+``` + +### 10.2 上传交互 + +JS 方法: + +```javascript +function bindUploadDropzone() +function uploadConversationFiles(files) +function renderAttachmentList(attachments) +``` + +流程: + +```text +用户拖拽或选择文件 +-> POST attachments 接口 +-> 保存成功后右侧上传区展示文件名 +-> 不启动工作流 +-> 用户发送提示词 +-> 命中工作流后创建工作流卡片 +``` + +### 10.3 工作流卡片 + +JS 方法: + +```javascript +function createWorkflowCard(batch) +function updateWorkflowNode(batchId, nodePayload) +function markWorkflowCompleted(batchId, payload) +function markWorkflowFailed(batchId, payload) +function connectWorkflowEvents(batchId) +function restoreWorkflowCards() +``` + +卡片结构: + +```html +
+
+ 文件目录与页数汇总 + 运行中 +
+
    +
  1. 上传中
  2. +
  3. 解压中
  4. +
  5. 扫描中
  6. +
  7. 解析页数中
  8. +
  9. 识别产品名中
  10. +
  11. 输出 Markdown 中
  12. +
  13. 输出 Excel 中
  14. +
+
+``` + +### 10.4 Markdown 渲染 + +现有消息使用 `nl2br`,无法正常渲染 Markdown 表格。需要改造: + +| 消息类型 | 渲染策略 | +| --- | --- | +| 普通用户消息 | escapeHtml + nl2br | +| 普通助手消息 | 安全 Markdown 渲染 | +| 文件汇总结果 | 安全 Markdown 渲染,允许 table、a、strong、code | + +可选方案: + +| 方案 | 说明 | +| --- | --- | +| 前端 marked + DOMPurify | 渲染体验好,但增加前端依赖 | +| 后端 markdown + bleach | 后端输出安全 HTML,前端直接展示 | + +Demo 建议使用前端 `marked` + `DOMPurify` CDN 或本地静态文件。 + +--- + +## 十一、对话标题更新设计 + +产品名识别成功后更新标题: + +```python +def update_conversation_title(batch, product_name): + conversation = batch.conversation + if conversation.title.startswith("新对话"): + conversation.title = f"{product_name}-文件汇总"[:120] + conversation.save(update_fields=["title", "updated_at"]) +``` + +规则: + +| 场景 | 处理 | +| --- | --- | +| 新对话默认标题 | 更新为产品名 | +| 用户已有自定义标题 | 不覆盖 | +| 产品名为空 | 不更新 | + +--- + +## 十二、测试设计 + +### 12.1 单元测试 + +| 用例 | 目标 | +| --- | --- | +| test_trigger_keywords | 提示词命中时触发自动汇总 | +| test_save_attachment_binds_conversation | 上传文件绑定当前对话 | +| test_zip_extract_safe_path | zip 解压禁止路径穿越 | +| test_scan_files_builds_relative_path | 扫描生成正确相对路径 | +| test_count_pdf_pages | PDF 页数统计 | +| test_count_xlsx_sheets | xlsx 工作表数量统计 | +| test_count_pptx_slides | pptx 幻灯片数量统计 | +| test_retry_three_times | 单文件失败重试 3 次 | +| test_uncertain_old_doc | 老 doc 元数据缺失时标记 uncertain | + +### 12.2 接口测试 + +| 用例 | 目标 | +| --- | --- | +| test_upload_attachment_api | 上传接口返回 attachment_id | +| test_upload_permission_denied | 不能向他人对话上传文件 | +| test_stream_triggers_workflow | 发送命中提示词后返回 workflow meta | +| test_batch_status_permission | 不能查询他人批次 | +| test_export_download_permission | 不能下载他人导出文件 | + +### 12.3 集成测试 + +| 用例 | 目标 | +| --- | --- | +| test_file_summary_zip_workflow | zip 上传后完整工作流成功 | +| test_file_summary_multi_file_workflow | 多文件上传后完整工作流成功 | +| test_single_file_failure_not_blocking | 单文件失败不阻断批次 | +| test_workflow_events_created | 节点事件按顺序写入数据库 | +| test_markdown_and_excel_exports | Markdown 与 Excel 文件生成成功 | + +### 12.4 前端验证 + +| 用例 | 目标 | +| --- | --- | +| 拖拽上传 | 右侧上传区展示文件列表 | +| 提示词触发 | 发送“自动汇总文件目录与页数”后创建工作流卡片 | +| 状态实时更新 | SSE 事件驱动节点状态变化 | +| 页面刷新恢复 | 刷新后右侧卡片恢复当前批次状态 | +| Markdown 表格 | 对话消息中表格和下载链接正常显示 | + +--- + +## 十三、开发顺序 + +1. 增加依赖与模型字段,生成迁移。 +2. 实现文件上传暂存接口和存储目录策略。 +3. 实现 workflow_trigger,根据提示词决定是否启动工作流。 +4. 实现 SkillRegistry、WorkflowExecutor 和 WorkflowEvent。 +5. 实现压缩包解压、文件扫描、页数统计服务。 +6. 实现 Markdown 报告与 Excel 导出。 +7. 改造前端三栏布局、拖拽上传区和工作流卡片。 +8. 增加 Markdown 渲染能力。 +9. 补齐权限测试、工作流测试和前端手工验证。 + +--- + +## 十四、参考依据 + +本设计采用轻量 Python 库优先方案,依据如下: + +| 能力 | 依据 | +| --- | --- | +| PDF 页数 | pypdf 的 PdfReader 可读取 pages | +| docx 元数据 | python-docx 支持 core properties | +| pptx 幻灯片 | python-pptx 可读取 presentation slides | +| xlsx 工作表 | openpyxl 可读取 workbook worksheets | +| xls 工作表 | xlrd 支持读取历史 xls 工作簿 | +| 老 Office 元数据 | olefile 可读取 OLE2 复合文档结构 | +| 7z 解压 | py7zr 支持 7z 压缩格式处理 | +| rar 解压 | rarfile 通常依赖外部 unrar/unar/7z 工具,故本设计优先系统 7z | diff --git a/docs/需求分析/1.自动汇总.md b/docs/需求分析/1.自动汇总.md new file mode 100644 index 0000000..9726de5 --- /dev/null +++ b/docs/需求分析/1.自动汇总.md @@ -0,0 +1,328 @@ +# 自动汇总文件夹文件目录与页数流程需求分析 + +## 文档信息 + +| 项目 | 内容 | +| --- | --- | +| 原始材料 | docs/原始材料/【模拟题二】试剂盒临床注册文件准备与审核Agent.docx | +| 功能主题 | 自动汇总注册申报资料文件目录与页数 | +| 分析日期 | 2026-06-05 | +| 分析版本 | V1.0 | + +--- + +## 一、需求背景 + +试剂盒 NMPA 注册申报过程中,研发或注册人员需要准备大量文件,包括产品技术要求、说明书、检测报告、临床评估资料等。原始材料中明确提出智能体需要具备“自动汇总注册申报文件夹中的所有文件及页数”的能力,作为后续法规完整性检查、缺失文件预警、信息提取和一致性核查的基础数据来源。 + +本功能目标是:用户在 AI 对话框上传注册申报资料压缩包、文件夹或多个散装文件后,系统自动扫描资料结构,统计各文件的目录层级、文件类型、页数、路径和处理状态,生成 Markdown 汇总报告与 Excel 文件,并在 AI 对话框中展示简表和下载链接,同时将汇总结果存入数据库。 + +--- + +## 二、需求范围 + +### 2.1 本期范围 + +| 序号 | 范围项 | 说明 | +| --- | --- | --- | +| 1 | 文件上传入口 | 用户通过现有 AI 对话框上传压缩包、文件夹或多个散装文件 | +| 2 | 文件解析 | 系统识别上传内容并展开为待扫描文件清单 | +| 3 | 目录汇总 | 保留原始目录层级,散装文件归入默认上传批次目录 | +| 4 | 页数统计 | 默认支持 pdf、doc、docx、xls、xlsx、ppt、pptx | +| 5 | 结果展示 | AI 对话框中展示 Markdown 简表 | +| 6 | 文件导出 | 生成 Markdown 报告与 Excel 汇总表,并在对话框提供下载链接 | +| 7 | 数据存档 | 上传批次、文件明细、页数、异常状态、导出文件信息写入数据库 | + +### 2.2 非本期范围 + +| 序号 | 范围项 | 说明 | +| --- | --- | --- | +| 1 | NMPA 法规完整性核查 | 依赖目录汇总结果,属于后续流程 | +| 2 | 产品关键信息抽取 | 依赖具体文档内容解析,属于后续流程 | +| 3 | 文档一致性核查 | 依赖信息抽取结果,属于后续流程 | +| 4 | 合规风险预警 | 本功能仅输出文件扫描异常,不输出法规合规风险 | + +--- + +## 三、用户角色与使用场景 + +| 角色 | 诉求 | 典型场景 | +| --- | --- | --- | +| 注册人员 | 快速了解申报资料是否完整、页数是否可统计 | 上传供应商或研发团队整理好的注册资料压缩包 | +| 审核人员 | 获得可复核的文件目录和页数清单 | 在审核前获取 Markdown 简表和 Excel 明细 | +| 系统管理员 | 追踪上传批次和处理异常 | 查看数据库存档,定位文件解析失败原因 | + +--- + +## 四、业务流程分析 + +### 4.1 主流程 + +```text +用户进入 AI 对话框 +-> 上传压缩包、文件夹或多个散装文件 +-> 用户发送“自动汇总文件目录与页数”类指令 +-> 系统创建上传批次 +-> 系统保存原始上传文件 +-> 系统识别上传类型 +-> 如为压缩包则解压,如为文件夹或散装文件则直接扫描 +-> 系统遍历文件并识别目录层级 +-> 系统过滤支持的文件类型 +-> 系统计数每个支持文件的页数 +-> 系统记录不支持或统计失败的文件异常 +-> 系统生成 Markdown 报告与 Excel 文件 +-> 系统在 AI 对话框展示 Markdown 简表和下载链接 +-> 系统将批次和明细数据写入数据库 +-> 结束 +``` + +### 4.2 分支流程 + +| 分支场景 | 流程说明 | +| --- | --- | +| 上传压缩包 | 系统校验压缩包格式,解压至临时工作目录,按解压后的目录结构统计 | +| 上传文件夹 | 系统保留文件夹层级,扫描所有子目录文件 | +| 上传多个散装文件 | 系统生成默认批次目录,将所有文件视为同一层级 | +| 存在不支持类型 | 文件进入明细表,页数为空,统计状态为“不支持”,异常说明记录文件类型 | +| 单个文件页数统计失败 | 不中断整个批次,统计状态为“失败”,异常说明记录失败原因 | +| 重名文件 | 以完整相对路径区分;散装文件重名时使用系统保存路径或自动重命名策略避免覆盖 | + +### 4.3 异常流程 + +| 异常场景 | 处理方式 | +| --- | --- | +| 上传文件为空 | 对话框提示“未检测到可处理文件”,不生成报告 | +| 压缩包损坏或无法解压 | 批次状态标记为失败,对话框展示错误原因 | +| 文件被加密或受保护 | 明细记录该文件,页数统计失败,异常说明标记“文件加密或无法读取” | +| 文件过大或处理超时 | 明细记录超时状态,批次可继续处理其他文件 | +| Excel/Markdown 导出失败 | 对话框提示导出失败,数据库保留扫描明细与失败原因 | + +--- + +## 五、功能模块梳理 + +### 5.1 核心功能 + +| 序号 | 功能名称 | 功能描述 | 优先级 | +| --- | --- | --- | --- | +| 1 | 上传资料接收 | 支持用户在 AI 对话框上传压缩包、文件夹或多个散装文件 | P0 | +| 2 | 上传批次管理 | 为每次汇总创建批次编号,记录用户、时间、来源会话和处理状态 | P0 | +| 3 | 压缩包解压 | 支持压缩包上传后的安全解压和目录结构保留 | P0 | +| 4 | 文件遍历扫描 | 递归扫描上传范围内所有文件,生成相对路径和目录层级 | P0 | +| 5 | 文件类型识别 | 根据扩展名和必要的文件头信息识别 pdf、doc、docx、xls、xlsx、ppt、pptx | P0 | +| 6 | 页数统计 | 对支持文件统计页数或页签/幻灯片数量 | P0 | +| 7 | Markdown 简表展示 | 在 AI 对话框展示可解析的 Markdown 表格摘要 | P0 | +| 8 | Markdown 报告导出 | 生成完整 Markdown 汇总报告 | P0 | +| 9 | Excel 明细导出 | 生成 Excel 文件,包含所有文件明细和统计状态 | P0 | +| 10 | 数据库存档 | 保存批次、文件明细、统计结果、异常说明和导出文件记录 | P0 | + +### 5.2 辅助功能 + +| 序号 | 功能名称 | 功能描述 | 优先级 | +| --- | --- | --- | --- | +| 1 | 下载链接生成 | 在 AI 回复中提供 Markdown 报告和 Excel 文件下载链接 | P0 | +| 2 | 处理进度提示 | 对较大批次展示“上传中、解析中、统计中、导出中、已完成”等状态 | P1 | +| 3 | 文件过滤规则 | 支持过滤系统隐藏文件、临时文件和空文件 | P1 | +| 4 | 统计摘要 | 输出文件总数、支持文件数、统计成功数、失败数、总页数 | P1 | +| 5 | 历史记录查询 | 可通过对话记录或批次记录回看历史汇总结果 | P1 | + +### 5.3 隐含功能 + +| 序号 | 功能名称 | 功能描述 | 来源说明 | +| --- | --- | --- | --- | +| 1 | AI 对话框附件上传能力 | 现有对话框需要支持上传附件并关联会话消息 | 用户要求“在 AI 对话框中提供下载连接” | +| 2 | Markdown 渲染能力 | 现有对话框需要能解析表格、链接等 Markdown 内容 | 用户要求“对话框能正常解析 md 格式” | +| 3 | 文件访问权限控制 | 下载链接只能允许当前用户或授权用户访问 | 涉及注册资料敏感文件 | +| 4 | 临时文件清理 | 解压目录和处理中间文件需要定期清理 | 压缩包和大文件处理会产生临时文件 | + +--- + +## 六、数据实体分析 + +### 6.1 实体列表 + +| 序号 | 实体名称 | 字段说明 | 关联实体 | +| --- | --- | --- | --- | +| 1 | 汇总批次 | 批次编号、用户、会话、上传类型、文件数量、总页数、状态、创建时间、完成时间 | 会话、文件明细、导出文件 | +| 2 | 文件明细 | 文件序号、目录层级、文件名、文件类型、页数、路径、统计状态、异常说明 | 汇总批次 | +| 3 | 导出文件 | 文件类型、文件名、保存路径、下载地址、生成状态、生成时间 | 汇总批次 | +| 4 | 上传原始文件 | 原始文件名、保存路径、大小、文件类型、上传时间 | 汇总批次 | + +### 6.2 文件明细字段 + +| 字段 | 类型建议 | 必填 | 说明 | +| --- | --- | --- | --- | +| file_index | Integer | 是 | 文件序号,按目录遍历顺序生成 | +| directory_level | String | 是 | 目录层级,如“1/2/3”或“注册资料/产品技术要求” | +| file_name | String | 是 | 文件名,不含目录 | +| file_type | String | 是 | 文件扩展名或识别后的类型 | +| page_count | Integer | 否 | 页数,统计失败或不支持时为空 | +| relative_path | String | 是 | 相对上传根目录的路径 | +| statistics_status | String | 是 | 成功、失败、不支持、跳过 | +| error_message | Text | 否 | 异常说明 | + +### 6.3 实体关系 + +```text +会话 1:N 汇总批次 +汇总批次 1:N 上传原始文件 +汇总批次 1:N 文件明细 +汇总批次 1:N 导出文件 +``` + +--- + +## 七、输出要求 + +### 7.1 AI 对话框简表 + +AI 回复内容必须使用前端可解析的 Markdown 格式,至少包含统计摘要、文件简表和下载链接。 + +示例: + +```markdown +已完成文件目录与页数汇总。 + +| 指标 | 数量 | +| --- | --- | +| 文件总数 | 24 | +| 统计成功 | 21 | +| 统计失败 | 2 | +| 不支持 | 1 | +| 总页数 | 386 | + +| 序号 | 目录层级 | 文件名 | 类型 | 页数 | 状态 | 异常说明 | +| --- | --- | --- | --- | --- | --- | --- | +| 1 | 注册资料/说明书 | 说明书.docx | docx | 12 | 成功 | | +| 2 | 注册资料/检测报告 | 检测报告.pdf | pdf | 38 | 成功 | | + +[下载 Markdown 报告](download-url) +[下载 Excel 明细](download-url) +``` + +### 7.2 Markdown 报告 + +Markdown 报告应包含: + +| 模块 | 内容 | +| --- | --- | +| 汇总信息 | 批次编号、上传用户、上传时间、处理完成时间、上传类型 | +| 统计摘要 | 文件总数、支持文件数、成功数、失败数、不支持数、总页数 | +| 文件明细 | 文件序号、目录层级、文件名、文件类型、页数、路径、统计状态、异常说明 | +| 异常清单 | 统计失败、不支持、解压失败、加密文件、超时文件 | +| 处理说明 | 支持范围、页数统计口径、待确认事项 | + +### 7.3 Excel 导出 + +Excel 至少包含两个工作表: + +| 工作表 | 内容 | +| --- | --- | +| 汇总信息 | 批次信息、统计摘要 | +| 文件明细 | 文件序号、目录层级、文件名、文件类型、页数、路径、统计状态、异常说明 | + +--- + +## 八、页数统计口径 + +| 文件类型 | 统计口径 | 备注 | +| --- | --- | --- | +| pdf | PDF 页面数量 | 可使用 pdfplumber 或 PyMuPDF | +| doc | Word 文档页数 | 可通过 LibreOffice 转 PDF 后统计,或读取文档属性;实现方案待技术验证 | +| docx | Word 文档页数 | 可通过 LibreOffice 转 PDF 后统计,或读取文档属性;实现方案待技术验证 | +| xls | Excel 工作表打印页数或页签数量 | 具体口径待确认 | +| xlsx | Excel 工作表打印页数或页签数量 | 具体口径待确认 | +| ppt | 幻灯片数量 | 可转换或读取演示文稿结构 | +| pptx | 幻灯片数量 | 可读取演示文稿结构 | + +> 待确认:Excel 的“页数”是否按打印分页统计,还是按工作表数量统计。建议 Demo 阶段先按工作表数量统计,并在报告中明确口径。 + +--- + +## 九、非功能性需求 + +### 9.1 性能要求 + +| 项目 | 要求 | +| --- | --- | +| 小批次文件 | 50 个文件以内,应在 30 秒内完成汇总 | +| 中等批次文件 | 200 个文件以内,应支持异步处理或进度提示 | +| 大文件处理 | 单文件超时不影响其他文件统计 | + +### 9.2 安全要求 + +| 项目 | 要求 | +| --- | --- | +| 上传安全 | 限制可处理类型,避免执行上传文件中的脚本或宏 | +| 解压安全 | 防止路径穿越,限制解压目录在系统工作目录内 | +| 下载权限 | 下载链接需要校验登录用户和会话权限 | +| 数据隔离 | 不同用户上传文件和统计结果不可互相访问 | +| 敏感资料保护 | 原始上传文件、报告和 Excel 文件应存储在受控目录 | + +### 9.3 可用性要求 + +| 项目 | 要求 | +| --- | --- | +| 对话展示 | Markdown 表格、链接在现有 AI 对话框中可正常渲染 | +| 失败可见 | 失败文件需要展示具体原因,便于用户补传或修复 | +| 可追溯 | 每份导出报告能关联到具体上传批次和会话 | + +--- + +## 十、疑问点与待确认事项 + +### 10.1 功能疑问 + +| 序号 | 疑问内容 | 建议 | 状态 | +| --- | --- | --- | --- | +| 1 | “文件目录参考附件”尚未上传,无法确认标准目录样例 | 附件上传后补充目录样例和字段映射 | 待确认 | +| 2 | 文件夹上传在浏览器端是否采用目录上传能力 | 前端可使用 webkitdirectory 或改为要求用户上传压缩包 | 待确认 | +| 3 | 散装文件是否需要用户手动指定归属目录 | Demo 阶段统一归入“散装上传”目录 | 待确认 | + +### 10.2 业务规则疑问 + +| 序号 | 疑问内容 | 建议 | 状态 | +| --- | --- | --- | --- | +| 1 | Excel 页数统计口径不明确 | Demo 阶段按工作表数量,正式版可按打印分页 | 待确认 | +| 2 | doc/docx 页数是否必须与 Word 打开后的实际页数完全一致 | 建议通过转换 PDF 后统计,提高一致性 | 待确认 | +| 3 | 是否需要按 NMPA 申报资料目录自动排序 | 本功能先按上传目录顺序,后续法规完整性检查再做标准目录匹配 | 待确认 | + +### 10.3 技术方案疑问 + +| 序号 | 疑问内容 | 可选方案 | 状态 | +| --- | --- | --- | --- | +| 1 | Office 文档页数统计依赖 | LibreOffice 转 PDF、文档属性读取、商业 Office 自动化 | 待确认 | +| 2 | 大文件处理模式 | 同步处理、后台任务、队列任务 | 待确认 | +| 3 | Markdown 渲染方案 | 前端引入 Markdown 渲染库,或后端转换为安全 HTML | 待确认 | + +### 10.4 数据定义疑问 + +| 序号 | 疑问内容 | 建议 | 状态 | +| --- | --- | --- | --- | +| 1 | 导出文件保存周期未明确 | Demo 阶段永久保存,正式版增加过期清理策略 | 待确认 | +| 2 | 原始上传文件是否长期保留 | 建议至少保留到关联审核流程结束 | 待确认 | +| 3 | 下载链接是否需要一次性或有效期控制 | 注册资料敏感,建议正式版增加有效期和权限校验 | 待确认 | + +--- + +## 十一、验收标准 + +| 序号 | 验收项 | 验收标准 | +| --- | --- | --- | +| 1 | 压缩包上传汇总 | 上传包含多层目录的压缩包后,系统能保留层级并统计支持文件页数 | +| 2 | 散装文件上传汇总 | 上传多个散装文件后,系统能生成同一批次汇总结果 | +| 3 | Markdown 展示 | AI 对话框能展示摘要表和文件简表,表格格式正常 | +| 4 | 下载链接 | AI 回复中提供 Markdown 报告和 Excel 明细下载链接,点击可下载 | +| 5 | 数据存档 | 数据库中能查询到批次记录、文件明细和导出文件记录 | +| 6 | 异常不中断 | 单个文件失败时不影响其他文件统计,失败原因可见 | +| 7 | 支持类型覆盖 | pdf、doc、docx、xls、xlsx、ppt、pptx 均能进入处理流程 | + +--- + +## 十二、下一步建议 + +1. 上传“文件目录参考附件”,补充标准目录样例和演示数据。 +2. 明确 Excel 页数统计口径,决定 Demo 版是否按工作表数量统计。 +3. 设计数据库表结构,包括汇总批次、文件明细、上传原始文件和导出文件。 +4. 改造 AI 对话框,增加附件上传、Markdown 渲染和下载链接展示能力。 +5. 实现文件扫描与页数统计服务,优先打通 pdf、docx、xlsx、pptx 的 Demo 流程。