docs(详细设计): 新增资料包导入与目录汇总设计

This commit is contained in:
2026-06-03 20:50:27 +08:00
parent 11c20593d5
commit 18428e75fd
7 changed files with 1501 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
# 压缩包解包Skill 设计
## 1. Skill 定位
`压缩包解包Skill` 负责压缩包解包,是资料包导入链路中的文件展开能力。它需要支持 `zip``rar``7z`,并确保解包过程安全、可追踪、可复核。
本 Skill 的输出是“解包后的文件树”,不是文档审核结论。
英文实现标识建议使用 `ArchiveExtractionSkill`,用于 Python 类名和 Tool Registry 注册处理器。
## 2. 输入
```python
@dataclass
class ArchiveExtractionInput:
archive_path: Path
target_dir: Path
source_archive_name: str
max_file_count: int = 1000
max_total_size: int | None = None
```
## 3. 输出
```python
@dataclass
class ArchiveExtractionOutput:
status: str
archive_type: str
extracted_files: list[ExtractedFile]
skipped_items: list[SkippedArchiveItem]
warnings: list[dict]
```
`ExtractedFile` 字段:
1. `absolute_path`
2. `relative_path`
3. `file_size`
4. `source_archive_name`
5. `file_hash`
## 4. 支持格式与技术
| 格式 | 技术 |
|---|---|
| `zip` | Python 标准库 `zipfile` |
| `rar` | 纯 Python rar 解析依赖,优先选择不依赖系统命令的库 |
| `7z` | `py7zr` |
V1 可以先完成 `zip`,同时保留 `rar``7z` 的接口和失败提示。正式验收前需要补齐纯 Python 解包能力。
## 5. 核心方法
### 5.1 `run(input) -> ArchiveExtractionOutput`
主入口方法。
处理顺序:
1. 识别压缩包类型。
2. 打开压缩包。
3. 遍历成员文件。
4. 校验成员路径安全性。
5. 校验数量和大小限制。
6. 解包到批次隔离目录。
7. 生成解包结果。
### 5.2 `detect_archive_type(path) -> str`
根据扩展名和文件头识别压缩包类型。
返回:
1. `zip`
2. `rar`
3. `7z`
4. `unsupported`
### 5.3 `validate_member_path(member_name) -> str`
路径安全校验。
拒绝:
1. 绝对路径。
2. 包含 `..` 的路径。
3. Windows 盘符路径。
4. 空路径。
5. 控制字符。
### 5.4 `extract_zip(input) -> list[ExtractedFile]`
使用 `zipfile` 解包。
### 5.5 `extract_rar(input) -> list[ExtractedFile]`
使用纯 Python rar 依赖解包。
如果当前环境依赖未安装,返回 `unsupported_archive`,并提示需要安装对应依赖。
### 5.6 `extract_7z(input) -> list[ExtractedFile]`
使用 `py7zr` 解包。
### 5.7 `build_file_hash(path) -> str`
使用 `sha256` 生成文件指纹,用于重复文件识别。
## 6. 状态设计
| 状态 | 含义 |
|---|---|
| `extracted` | 解包成功 |
| `partial_extracted` | 部分成员解包失败 |
| `empty_archive` | 压缩包为空 |
| `encrypted_archive` | 加密压缩包 |
| `unsupported_archive` | 格式不支持 |
| `path_rejected` | 存在危险路径 |
| `extract_failed` | 解包失败 |
## 7. 异常处理
1. 加密压缩包:不尝试暴力解析,标记为待人工处理。
2. 路径穿越:拒绝危险成员并记录安全拦截。
3. 超大压缩包:超过配置限制时停止处理。
4. 成员数量过多:停止处理并提示资料包异常。
5. 文件名乱码:保留原始名称,展示层提示人工复核。
## 8. 测试要点
1. 普通 zip 解包成功。
2. 多层目录相对路径被保留。
3. `../evil.docx` 被拒绝。
4. 空压缩包返回 `empty_archive`
5. 不支持格式返回 `unsupported_archive`
6. 解包后文件哈希生成稳定。

View File

@@ -0,0 +1,126 @@
# 文档页数统计Skill 设计
## 1. Skill 定位
`文档页数统计Skill` 负责为注册申报资料文件生成页数统计结果。页数是题面明确要求的关键指标,因此本 Skill 必须把页数、统计方法和可信度分开记录。
本 Skill 不负责文档正文抽取,不负责 OCR不负责合规判断。
英文实现标识建议使用 `DocumentPageCountSkill`,用于 Python 类名和 Tool Registry 注册处理器。
## 2. 输入
```python
@dataclass
class DocumentPageCountInput:
document_id: int
file_path: Path
file_type: str
options: dict = field(default_factory=dict)
```
## 3. 输出
```python
@dataclass
class PageCountResult:
document_id: int
page_count: int | None
method: str
confidence: str
status: str
message: str = ""
```
## 4. 页数统计策略
| 文件类型 | 策略 | 可信度 |
|---|---|---|
| `pdf` | 使用 `pypdf``PyMuPDF` 读取页数 | `exact` |
| `docx` | 优先读取 Word 统计信息,必要时转换 PDF 后统计 | `exact` |
| `doc` | 尝试转换后统计,失败则待复核 | `manual_review_required` |
| `txt` | 页数不适用 | `not_applicable` |
| `md` | 页数不适用 | `not_applicable` |
DOCX 是 V1 验收重点,不能用字数、段落数或估算分页代替精确页数。
## 5. 核心方法
### 5.1 `run(input) -> PageCountResult`
根据文件类型路由到具体统计方法。
### 5.2 `count_pdf_pages(path) -> PageCountResult`
推荐使用 `pypdf``PyMuPDF`
失败处理:
1. 文件损坏:`failed`
2. 加密 PDF`manual_review_required`
3. 无法读取:`failed`
### 5.3 `count_docx_pages(path) -> PageCountResult`
DOCX 精确页数建议采用两级策略:
1. 读取文档内部统计属性中的页数。
2. 若统计属性不可用,使用 LibreOffice headless 转 PDF再统计 PDF 页数。
如果两级策略均失败,输出 `manual_review_required`,并在页面突出显示。
### 5.4 `count_doc_pages(path) -> PageCountResult`
DOC 文件首版策略:
1. 尝试用兼容转换工具转 PDF。
2. 转换成功后统计 PDF 页数。
3. 转换失败则标记待人工复核。
### 5.5 `count_text_pages(path) -> PageCountResult`
TXT/MD 首版不做页数强制验收。
返回:
1. `page_count = None`
2. `method = "not_applicable"`
3. `confidence = "not_applicable"`
## 6. 技术实现
建议依赖:
1. `pypdf`
2. `PyMuPDF`
3. `python-docx`
4. LibreOffice headless可作为增强能力
如果 V1 Docker 环境暂不内置 LibreOffice应在配置中显式标注 `DOCX_PAGE_COUNT_STRATEGY`,并保证演示样本能通过已实现策略得到精确页数。
## 7. 状态设计
| 状态 | 含义 |
|---|---|
| `success` | 页数统计成功 |
| `not_applicable` | 文本类文件不适用 |
| `manual_review_required` | 需要人工复核 |
| `failed` | 统计失败 |
## 8. 落库字段
建议写入 `RegistrationDocument`
1. `page_count`
2. `page_count_method`
3. `page_count_confidence`
4. `page_count_status`
5. `processing_message`
## 9. 测试要点
1. PDF 返回精确页数。
2. DOCX 返回精确页数。
3. DOC 无法统计时标记待人工复核。
4. TXT/MD 返回不适用。
5. 损坏文件返回失败状态。

View File

@@ -0,0 +1,185 @@
# 目录汇总Skill 设计
## 1. Skill 定位
`目录汇总Skill` 负责把一个资料包批次中的文档主数据聚合为目录汇总报告。它是第一步“资料包导入与目录汇总”的最终输出 Skill。
本 Skill 不重新扫描文件,不重新统计页数,只消费已经落库或已处理的文档事实。
英文实现标识建议使用 `DirectorySummarySkill`,用于 Python 类名和 Tool Registry 注册处理器。
## 2. 输入
```python
@dataclass
class DirectorySummaryInput:
batch_id: int
include_unsupported: bool = true
include_warnings: bool = true
```
## 3. 输出
```python
@dataclass
class RegistrationOverviewReport:
batch_id: int
batch_no: str
workflow_type: str
source_role: str
file_count: int
supported_file_count: int
unsupported_file_count: int
failed_file_count: int
manual_review_count: int
total_page_count: int
page_count_status: str
chapter_summary: list[dict]
documents: list[dict]
warnings: list[dict]
```
## 4. 核心方法
### 4.1 `run(input) -> RegistrationOverviewReport`
主入口方法。
执行顺序:
1. 读取批次信息。
2. 读取该批次所有文档记录。
3. 计算总文件数。
4. 计算支持文件、不支持文件、失败文件和待复核文件数量。
5. 汇总页数。
6. 按章节点聚合。
7. 生成文档明细。
8. 生成导入警告。
9. 返回结构化报告。
### 4.2 `summarize_counts(documents) -> dict`
计算:
1. `file_count`
2. `supported_file_count`
3. `unsupported_file_count`
4. `failed_file_count`
5. `manual_review_count`
### 4.3 `summarize_pages(documents) -> dict`
计算:
1. `total_page_count`
2. `exact_page_count`
3. `manual_review_page_count`
4. `page_count_status`
`page_count_status` 规则:
1. 全部精确:`exact`
2. 部分待复核:`partial_review_required`
3. 全部无法统计:`manual_review_required`
### 4.4 `summarize_chapters(documents) -> list[dict]`
`chapter_code` 聚合:
1. 文件数。
2. 页数。
3. 待复核数。
4. 主要资料角色。
无章节点的文件归入 `unclassified`
### 4.5 `build_document_rows(documents) -> list[dict]`
生成页面明细。
字段:
1. `document_id`
2. `relative_path`
3. `original_filename`
4. `file_type`
5. `page_count`
6. `page_count_confidence`
7. `chapter_code`
8. `chapter_name`
9. `document_role`
10. `processing_status`
11. `needs_manual_review`
### 4.6 `build_warnings(documents) -> list[dict]`
生成业务化提示。
提示类型:
1. `unsupported_file`
2. `page_count_review_required`
3. `chapter_unclassified`
4. `file_type_mismatch`
5. `duplicate_file`
6. `archive_extract_warning`
## 5. 技术实现
使用技术:
1. Django ORM 聚合查询
2. Python dataclass 或 Pydantic
3. JSONField 存储报告快照
建议注册名:
```python
tool_registry.register(
name="build_directory_summary",
handler=DirectorySummarySkill().run,
)
```
## 6. 报告存储
建议将目录汇总报告快照写入批次模型或独立模型:
```python
class SubmissionBatchSummary(models.Model):
batch = models.OneToOneField(SubmissionBatch, on_delete=models.CASCADE)
report_type = models.CharField(max_length=64, default="registration_overview_report")
report_json = models.JSONField(default=dict)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
```
## 7. 与页面接口
Documents 页面直接消费 `RegistrationOverviewReport`
1. 顶部显示统计卡片。
2. 中部显示按章节点聚合表。
3. 底部显示文件明细。
4. 侧边显示待人工复核提示。
## 8. 与后续 Agent Core 接口
法规完整性核查从本报告读取:
1. `batch_id`
2. `documents`
3. `chapter_summary`
4. `manual_review_count`
5. `warnings`
如果 `manual_review_count > 0`,后续完整性核查仍可执行,但最终报告应标记“存在资料处理不确定性”。
## 9. 测试要点
1. 空批次返回合理空报告。
2. 多章节点文件能正确聚合。
3. 页数合计只统计有明确页数的文档。
4. 待复核文件数量正确。
5. 不支持文件可选展示。
6. 输出 schema 字段稳定。

View File

@@ -0,0 +1,158 @@
# 章节点识别Skill 设计
## 1. Skill 定位
`章节点识别Skill` 负责对已登记文档进行章节点和资料角色的初步识别,为目录汇总和后续法规完整性核查提供结构化字段。
本 Skill 使用规则优先,不依赖 LLM。后续可以在人工复核或复杂标题解析中引入模型辅助但不作为 V1 必需能力。
英文实现标识建议使用 `ChapterClassificationSkill`,用于 Python 类名和 Tool Registry 注册处理器。
## 2. 输入
```python
@dataclass
class ChapterClassificationInput:
document_id: int
original_filename: str
relative_path: str
file_type: str
title_text: str | None = None
manual_hint: dict = field(default_factory=dict)
```
## 3. 输出
```python
@dataclass
class ChapterClassificationResult:
document_id: int
chapter_code: str | None
chapter_name: str | None
document_role: str | None
declared_document_name: str | None
confidence: str
status: str
evidence: list[dict]
```
## 4. 识别规则
### 4.1 章节点编码识别
从文件名和相对路径中识别:
1. `CH1.2`
2. `CH1.4`
3. `CH1.5`
4. `CH1.9`
5. `CH1.11.1`
6. `CH1.11.5`
7. `CH1.11.6`
正则示例:
```python
r"CH\s*(\d+(?:\.\d+)*)"
```
### 4.2 章节名称识别
从相对路径中识别:
1. `第1章 监管信息`
2. `第2章 综述资料`
3. `第3章 非临床资料`
4. `第4章 临床评价资料`
5. `第5章 产品说明书和标签样稿`
6. `第6章 质量管理体系文件`
### 4.3 文档角色识别
| 关键词 | document_role |
|---|---|
| `监管信息目录` | `regulatory_information_catalog` |
| `申请表` | `application_form` |
| `产品列表` | `product_list` |
| `符合标准的清单` | `standard_compliance_list` |
| `真实性声明` | `authenticity_statement` |
| `符合性声明` | `conformity_statement` |
| `沟通的说明` | `pre_submission_communication` |
| `说明书` | `product_instruction` |
## 5. 核心方法
### 5.1 `run(input) -> ChapterClassificationResult`
主入口方法。
执行顺序:
1. 从相对路径识别章名称。
2. 从文件名识别章节点编码。
3. 从文件名识别文档角色。
4. 如有标题文本,则用标题补充识别。
5. 计算置信度。
6. 返回识别结果。
### 5.2 `extract_chapter_code(text) -> str | None`
从路径或文件名提取 `CHx.x`
### 5.3 `extract_chapter_name(relative_path) -> str | None`
从目录层级识别章节名称。
### 5.4 `detect_document_role(text) -> str | None`
基于关键词和规则表识别文档角色。
### 5.5 `calculate_confidence(matches) -> str`
置信度规则:
1. 路径、文件名和标题一致:`high`
2. 文件名命中但路径缺失:`medium`
3. 只有关键词命中:`low`
4. 无法识别:`manual_review_required`
## 6. 技术实现
使用技术:
1. `re`
2. YAML 规则表
3. 可选 `python-docx` 首页标题抽取
4. Django 管理后台人工修正
建议规则文件:
```text
configs/registration/chapter_classification.yaml
```
## 7. 落库字段
建议写入 `RegistrationDocument`
1. `chapter_code`
2. `chapter_name`
3. `document_role`
4. `declared_document_name`
5. `classification_confidence`
6. `classification_status`
7. `needs_manual_review`
## 8. 异常处理
1. 文件名无章节点:尝试路径识别。
2. 路径与文件名冲突:标记待人工复核。
3. 识别为法规资料但批次为业务资料:标记潜在混入风险。
4. 同一文件命中多个角色:保留最高优先级角色,记录警告。
## 9. 测试要点
1. `CH1.4 申请表.docx` 识别为 `CH1.4``application_form`
2. `第1章 监管信息/CH1.2 监管信息目录.docx` 识别章节和目录角色。
3. 无章节点文件标记待人工复核。
4. 路径与文件名冲突时输出警告。

View File

@@ -0,0 +1,168 @@
# 资料包导入Skill 设计
## 1. Skill 定位
`资料包导入Skill` 是资料包导入工作流的总入口 Skill负责把用户上传的文件集合转化为一个可处理的资料包批次并协调解包、扫描、文档登记、页数统计、章节点识别和目录汇总。
它不直接处理法规完整性,不调用 LLM不执行 RAG 入库。
英文实现标识建议使用 `SubmissionPackageImportSkill`,用于 Python 类名和 Tool Registry 注册处理器。
## 2. 触发场景
1. 用户在 Web 工作台上传单文件。
2. 用户批量上传多个文件。
3. 用户上传压缩包。
4. 飞书入口后续触发资料包导入任务。
5. 管理员导入平台内置法规资料。
## 3. 输入
```python
@dataclass
class SubmissionPackageImportInput:
files: list[UploadedFileRef]
batch_name: str
workflow_type: str = "registration"
source_role: str = "submission"
created_by_id: int | None = None
import_options: dict = field(default_factory=dict)
```
字段说明:
| 字段 | 说明 |
|---|---|
| `files` | 用户上传文件引用 |
| `batch_name` | 资料包批次名称 |
| `workflow_type` | 注册流程类型V1 默认 `registration` |
| `source_role` | `submission``regulation` |
| `created_by_id` | 操作人 |
| `import_options` | 是否立即页数统计、是否跳过不支持文件等选项 |
## 4. 输出
```python
@dataclass
class SubmissionPackageImportOutput:
batch_id: int
batch_no: str
status: str
overview_report: dict
warnings: list[dict]
failed_items: list[dict]
```
## 5. 依赖 Skill
1. `压缩包解包Skill`
2. `资料包扫描Skill`
3. `文档页数统计Skill`
4. `章节点识别Skill`
5. `目录汇总Skill`
## 6. 核心方法
### 6.1 `run(input) -> SubmissionPackageImportOutput`
主入口方法。
执行顺序:
1. 创建 `SubmissionBatch`
2. 保存上传文件到批次隔离目录。
3. 对压缩包调用 `压缩包解包Skill`
4. 调用 `资料包扫描Skill` 扫描批次目录。
5. 创建 `RegistrationDocument` 记录。
6. 调用 `文档页数统计Skill`
7. 调用 `章节点识别Skill`
8. 调用 `目录汇总Skill`
9. 更新批次状态。
10. 返回目录汇总结果。
### 6.2 `create_batch(input) -> SubmissionBatch`
创建资料包批次。
状态:
1. 初始为 `created`
2. 文件保存开始后更新为 `importing`
3. 汇总阶段更新为 `summarizing`
4. 成功后更新为 `completed``partial_completed`
### 6.3 `save_uploaded_files(batch, files) -> list[StoredUpload]`
保存原始上传文件,并记录上传文件来源。
安全要求:
1. 过滤路径穿越。
2. 原始文件名只用于展示。
3. 真实存储路径使用批次目录和安全文件名。
### 6.4 `create_document_records(batch, scanned_files) -> list[RegistrationDocument]`
把扫描结果落库为文档主数据。
落库字段:
1. `original_filename`
2. `relative_path`
3. `file_type`
4. `file_size`
5. `file_hash`
6. `source_archive_name`
7. `source_role`
8. `workflow_type`
9. `processing_status`
### 6.5 `finalize_batch_status(batch, overview_report) -> str`
根据结果决定批次状态。
规则:
1. 全部成功:`completed`
2. 存在待人工复核:`partial_completed`
3. 存在不支持文件但有有效文件:`partial_completed`
4. 无有效文件:`failed`
## 7. 技术实现
使用技术:
1. Django ORM
2. Django Storage
3. `pathlib`
4. `dataclasses` 或 Pydantic
5. Tool Registry
建议注册名:
```python
tool_registry.register(
name="submission_package_import",
handler=SubmissionPackageImportSkill().run,
)
```
## 8. 异常处理
| 异常 | 处理 |
|---|---|
| 上传文件为空 | 拒绝该文件,记录失败项 |
| 批次目录创建失败 | 整体失败 |
| 解包失败 | 批次部分失败或整体失败 |
| 无支持文件 | 批次失败 |
| 页数统计部分失败 | 批次部分完成 |
| 章节点识别失败 | 文档标记待人工确认 |
## 9. 测试要点
1. 单文件导入成功。
2. 多文件导入共用同一批次。
3. 压缩包导入保留相对路径。
4. 不支持文件不会阻断有效文件。
5. 部分失败时批次为 `partial_completed`
6. 输出包含 `overview_report`

View File

@@ -0,0 +1,127 @@
# 资料包扫描Skill 设计
## 1. Skill 定位
`资料包扫描Skill` 负责扫描资料包根目录,生成后续文档登记、页数统计和章节点识别所需的文件清单。
它处理的是文件系统事实,不读取文档正文,不做页数统计。
英文实现标识建议使用 `PackageFileScanSkill`,用于 Python 类名和 Tool Registry 注册处理器。
## 2. 输入
```python
@dataclass
class PackageFileScanInput:
root_dir: Path
batch_id: int
source_role: str = "submission"
allowed_extensions: set[str] = field(default_factory=set)
```
## 3. 输出
```python
@dataclass
class PackageFileScanOutput:
scanned_files: list[ScannedFile]
unsupported_files: list[ScannedFile]
skipped_files: list[SkippedFile]
warnings: list[dict]
```
`ScannedFile` 字段:
1. `absolute_path`
2. `relative_path`
3. `original_filename`
4. `extension`
5. `file_size`
6. `file_hash`
7. `source_role`
## 4. 核心方法
### 4.1 `run(input) -> PackageFileScanOutput`
主入口方法。
执行顺序:
1. 遍历根目录。
2. 过滤目录和临时文件。
3. 识别文件类型。
4. 生成文件大小和哈希。
5. 区分支持文件、不支持文件和跳过文件。
6. 返回扫描结果。
### 4.2 `iter_files(root_dir) -> Iterator[Path]`
使用 `pathlib.Path.rglob("*")` 遍历文件。
### 4.3 `should_skip(path) -> bool`
跳过规则:
1. `.DS_Store`
2. `Thumbs.db`
3. `__MACOSX`
4. Office 临时文件 `~$*.docx`
5. 空文件
### 4.4 `is_supported_document(path) -> bool`
支持类型:
1. `pdf`
2. `docx`
3. `doc`
4. `txt`
5. `md`
压缩包在本 Skill 中不再进入支持文档列表,应由导入入口先交给 `压缩包解包Skill`
### 4.5 `build_relative_path(root_dir, path) -> str`
生成统一的相对路径,使用 `/` 作为内部存储分隔符。
### 4.6 `build_file_hash(path) -> str`
使用 `sha256`
## 5. 技术实现
使用技术:
1. `pathlib`
2. `hashlib`
3. `mimetypes`
4. 可选 `python-magic`
## 6. 输出用途
扫描结果用于:
1. 创建 `RegistrationDocument`
2. 保留资料包原始目录结构。
3. 识别重复文件。
4. 后续章节点识别。
5. 页面展示不支持文件提示。
## 7. 异常处理
| 异常 | 处理 |
|---|---|
| 根目录不存在 | 返回失败 |
| 文件读取失败 | 加入 `skipped_files` |
| 文件为空 | 加入 `skipped_files` |
| 类型不支持 | 加入 `unsupported_files` |
| 哈希计算失败 | 保留文件记录,标记警告 |
## 8. 测试要点
1. 多层目录扫描后相对路径正确。
2. Office 临时文件被跳过。
3. 不支持文件进入 `unsupported_files`
4. 支持文件进入 `scanned_files`
5. 哈希对同一文件稳定。