docs(详细设计): 新增资料包导入与目录汇总设计
This commit is contained in:
137
docs/详细设计/skill/压缩包解包Skill.md
Normal file
137
docs/详细设计/skill/压缩包解包Skill.md
Normal 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. 解包后文件哈希生成稳定。
|
||||
126
docs/详细设计/skill/文档页数统计Skill.md
Normal file
126
docs/详细设计/skill/文档页数统计Skill.md
Normal 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. 损坏文件返回失败状态。
|
||||
185
docs/详细设计/skill/目录汇总Skill.md
Normal file
185
docs/详细设计/skill/目录汇总Skill.md
Normal 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 字段稳定。
|
||||
158
docs/详细设计/skill/章节点识别Skill.md
Normal file
158
docs/详细设计/skill/章节点识别Skill.md
Normal 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. 路径与文件名冲突时输出警告。
|
||||
168
docs/详细设计/skill/资料包导入Skill.md
Normal file
168
docs/详细设计/skill/资料包导入Skill.md
Normal 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`。
|
||||
127
docs/详细设计/skill/资料包扫描Skill.md
Normal file
127
docs/详细设计/skill/资料包扫描Skill.md
Normal 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. 哈希对同一文件稳定。
|
||||
Reference in New Issue
Block a user