# 产品关键信息提取与申报文件自动填表详细设计 ## 文档信息 | 项目 | 内容 | | --- | --- | | 需求分析文档 | docs/1.需求分析/3.产品关键信息提取与申报文件自动填表.md | | 功能设计文档 | docs/2.功能设计/3.产品关键信息提取与申报文件自动填表.md | | 数据库设计文档 | docs/4.数据库设计/3.产品关键信息提取与申报文件自动填表.md | | 依赖详细设计 | docs/3.详细设计/1.自动汇总.md;docs/3.详细设计/2.NMPA注册资料法规核查与整改闭环.md | | 功能名称 | 产品关键信息提取与申报文件自动填表 | | 所属模块 | 审核智能体 review_agent | | 设计日期 | 2026-06-07 | | 设计版本 | V1.0 | --- ## 一、详细设计目标 本详细设计用于指导“产品关键信息提取与申报文件自动填表”功能开发落地,覆盖代码结构、数据库模型、模板配置、独立工作流、字段抽取、字段合并、Word 模板填充、追溯清单导出、飞书通知、接口契约、前端卡片、异常降级和测试建议。 核心约束: | 约束 | 说明 | | --- | --- | | 独立工作流 | 使用 `workflow_type=application_form_fill`,拥有独立批次和卡片 | | 对话触发 | 由用户自然语言触发,可指定模板;未指定时按注册类型选择适用模板 | | 文件来源复用 | 默认使用当前对话最近成功的 `FileSummaryBatch`;本次带附件时先执行自动汇总 | | 模板配置驱动 | 模板路径、字段映射、适用条件写入 `application_form_fill/templates/application_form_templates_v1.yaml` | | Word 优先 | Demo 阶段主链路只要求生成 Word 和追溯清单 | | PDF 待办 | PDF 转换节点保留,但本期可标记 skipped 并写入待办计划 | | 抽取并行 | 规则/正则抽取与 LLM 结构化抽取并行执行,再统一合并 | | 冲突可见 | 说明书优先;冲突字段写入 Word 时黄底红字,并在对话框展示摘要 | | 过程留底 | 规则抽取、LLM 抽取、合并结果、冲突和追溯清单均保存产物 | | 飞书通知 | 填表完成后通知上传人,通知失败不阻断下载 | --- ## 二、代码结构设计 ### 2.1 目录结构 第三批独立为 `review_agent/application_form_fill/` 模块。Django 模型仍集中在 `review_agent/models.py`,业务服务放入独立模块。 ```text review_agent/ models.py services.py skill_router.py application_form_fill/ __init__.py constants.py schemas.py storage.py workflow.py views.py services/ __init__.py template_config.py template_select.py template_repository.py field_extract.py field_merge.py word_fill.py traceability_export.py notifier.py templates/ application_form_templates_v1.yaml prompts/ field_extract.md checklist_extract.md ``` ### 2.2 文件职责 | 文件 | 职责 | | --- | --- | | application_form_fill/constants.py | 工作流节点、模板编码、状态、输出类型常量 | | application_form_fill/schemas.py | FormFillContext、TemplateSpec、ExtractedField、MergedField 等 dataclass | | application_form_fill/storage.py | 批次工作目录、模板副本、产物保存、hash 计算 | | application_form_fill/workflow.py | FormFillWorkflowExecutor,串行执行独立填表工作流 | | application_form_fill/views.py | 启动、状态查询、后续可选下载或重试接口 | | services/template_config.py | 读取和校验 YAML 模板配置 | | services/template_select.py | 解析用户指定模板、识别注册类型、选择模板 | | services/template_repository.py | 定位原始模板、复制模板、`.doc` 转 `.docx` 预留 | | services/field_extract.py | 规则/正则与 LLM 并行字段抽取 | | services/field_merge.py | 字段归一化、来源排序、冲突识别、最终字段输出 | | services/word_fill.py | 使用 `python-docx` 写入 Word 表格、段落和高亮 | | services/traceability_export.py | 生成 Excel/JSON 追溯清单,创建导出记录 | | services/notifier.py | 包装飞书通知,生成通知记录 | | prompts/field_extract.md | LLM 字段抽取提示词 | | prompts/checklist_extract.md | 安全和性能基本原则清单条目判断提示词 | --- ## 三、依赖设计 ### 3.1 Python 依赖 | 依赖 | 用途 | 当前项目情况 | | --- | --- | --- | | Django | Web、ORM、权限 | 已使用 | | python-docx | Word 模板读取、表格填充、字体和底色设置 | 已在项目依赖链中使用 | | openpyxl | 字段来源追溯清单 Excel 导出 | 已使用 | | PyYAML | YAML 模板配置读取 | 已用于法规规则 | | pypdf / python-pptx | 文本抽取链路复用 | 已使用 | | LibreOffice/soffice | `.doc` 转 `.docx`、PDF 转换 | 本期非强依赖,后续待办 | ### 3.2 技术边界 | 能力 | 本期实现 | 后续增强 | | --- | --- | --- | | `.docx` 模板填充 | 必须支持 | 支持内容控件、复杂 OOXML patch | | `.doc` 模板处理 | 可通过预转换模板或标记失败 | 自动 LibreOffice 转换 | | PDF 转换 | 可跳过并提示待生成 | LibreOffice 转 PDF + 视觉 QA | | 字段级入库 | 不做 | 新增字段明细表和在线编辑 | | LLM 抽取 | 输出 JSON 并留底 | 增加置信度校准和人工确认 | --- ## 四、数据模型详细设计 模型放在 `review_agent/models.py`。 ### 4.1 ApplicationFormFillBatch ```python class ApplicationFormFillBatch(models.Model): class Status(models.TextChoices): PENDING = "pending", "待执行" RUNNING = "running", "执行中" WAITING_USER = "waiting_user", "等待用户" SUCCESS = "success", "成功" PARTIAL_SUCCESS = "partial_success", "部分成功" FAILED = "failed", "失败" CANCELLED = "cancelled", "已取消" ``` 关键字段: | 字段 | 说明 | | --- | --- | | conversation | 绑定对话 | | user | 发起用户 | | trigger_message | 触发消息 | | source_summary_batch | 文件来源批次 | | source_regulatory_batch | 可选法规核查批次 | | batch_no | `AFF-YYYYMMDDHHMMSS-abcdef` | | requested_templates | 用户指定模板 | | selected_templates | 实际生成模板 | | output_types | 本次请求输出类型,Demo 默认 `["word", "excel", "json"]` | | registration_type | 识别出的注册类型 | | registration_type_source | 注册类型来源 | | product_name | 产品名称 | | conflict_summary | 冲突摘要 | | risk_notes | 不适用模板、PDF 待生成等提示 | | template_config_version | 模板配置版本 | | template_config_hash | 模板配置 hash | | work_dir | 批次工作目录 | ### 4.2 ApplicationFormFillArtifact 用于保存过程产物和模板副本元数据。 ```python class ApplicationFormFillArtifact(models.Model): class ArtifactType(models.TextChoices): TEMPLATE_COPY = "template_copy", "模板副本" FIELD_EXTRACT_RESULT = "field_extract_result", "字段抽取结果" MERGED_FIELDS = "merged_fields", "字段合并结果" TRACEABILITY = "traceability", "追溯清单" FILLED_TEMPLATE = "filled_template", "已填模板" NOTIFICATION_RECORD = "notification_record", "通知记录" ``` ### 4.3 ApplicationFormFillNotificationRecord 通知记录字段与第二批法规通知风格一致,支持重试: | 字段 | 说明 | | --- | --- | | batch | 自动填表批次 | | recipient | 通知对象 | | channel | feishu_cli、feishu_api、mock | | template_codes | 涉及模板 | | export_ids | 关联下载文件 | | message_summary | 通知摘要 | | send_status | pending、success、failed | | retry_count | 重试次数 | | external_message_id | 飞书外部消息 ID | | error_message | 失败原因 | | sent_at | 发送成功时间 | ### 4.4 ExportedSummaryFile 扩展 `ExportedSummaryFile.ExportType` 增加: ```python WORD = "word", "Word" PDF = "pdf", "PDF" ``` 填表导出记录使用: | 字段 | 值 | | --- | --- | | workflow_type | application_form_fill | | workflow_batch_id | ApplicationFormFillBatch.id | | export_category | filled_template、traceability、extract_result | | export_type | word、excel、json、pdf | --- ## 五、常量设计 ### 5.1 工作流节点 ```python FORM_FILL_NODE_DEFINITIONS = [ ("prepare", "准备资料", "form_fill"), ("template_select", "选择模板", "form_fill"), ("template_copy", "复制模板", "form_fill"), ("field_extract", "抽取字段", "form_fill"), ("conflict_merge", "冲突归并", "form_fill"), ("word_fill", "填写 Word", "form_fill"), ("pdf_convert", "转换 PDF", "form_fill"), ("trace_export", "追溯清单", "form_fill"), ("output_export", "输出下载", "form_fill"), ("notify", "飞书通知", "form_fill"), ("completed", "完成", "completed"), ] ``` ### 5.2 模板编码 ```python TEMPLATE_REGISTRATION_CERTIFICATE = "registration_certificate" TEMPLATE_CHANGE_REGISTRATION = "change_registration" TEMPLATE_ESSENTIAL_PRINCIPLES = "essential_principles" ``` ### 5.3 触发关键词 ```python FORM_FILL_TRIGGER_KEYWORDS = [ "填注册证", "对应的表格", "生成申报模板", "安全和性能基本原则清单", "填到申报模板", "自动填表", "生成表格", ] ``` --- ## 六、核心数据结构 ### 6.1 FormFillContext ```python @dataclass class FormFillContext: batch: ApplicationFormFillBatch source_summary_batch: FileSummaryBatch source_regulatory_batch: RegulatoryReviewBatch | None template_config: dict[str, Any] selected_templates: list["TemplateSpec"] document_texts: dict[str, str] regex_results: dict[str, Any] llm_results: dict[str, Any] merged_fields: dict[str, "MergedField"] checklist_items: dict[str, Any] conflicts: list[dict[str, Any]] exports: list[ExportedSummaryFile] ``` ### 6.2 TemplateSpec ```python @dataclass(frozen=True) class TemplateSpec: code: str name: str source_file: str output_label: str applies_when: dict[str, Any] file_format: str fields: list[dict[str, Any]] checklist_items: list[dict[str, Any]] ``` ### 6.3 ExtractedField ```python @dataclass(frozen=True) class ExtractedField: key: str label: str value: str source_file: str source_role: str evidence: str extractor: str confidence: float ``` ### 6.4 MergedField ```python @dataclass(frozen=True) class MergedField: key: str label: str value: str source_file: str evidence: str confidence: float has_conflict: bool = False conflict_values: list[dict[str, Any]] = field(default_factory=list) ``` --- ## 七、模板配置详细设计 ### 7.1 配置路径 ```text review_agent/application_form_fill/templates/application_form_templates_v1.yaml ``` ### 7.2 初始配置示例 ```yaml version: application_form_templates_v1 source_dir: docs/0.原始材料/关于公布体外诊断试剂注册申报资料要求和批准证明文件格式的公告 templates: - code: registration_certificate name: 中华人民共和国医疗器械注册证(体外诊断试剂)(格式) source_file: 中华人民共和国医疗器械注册证(体外诊断试剂)(格式).docx output_label: 注册证格式 applies_when: registration_type: ["首次注册"] file_format: docx fields: - key: applicant_name label: 注册人名称 target: type: table_row row_label: 注册人名称 source_roles: ["申请表", "说明书", "企业信息"] - key: product_name label: 产品名称 target: type: table_row row_label: 产品名称 source_roles: ["说明书", "产品技术要求", "注册检验报告"] - key: intended_use label: 预期用途 target: type: table_row row_label: 预期用途 source_roles: ["说明书", "临床评价资料", "产品技术要求"] ``` ### 7.3 配置校验 `TemplateConfigService` 启动时校验: | 校验项 | 失败处理 | | --- | --- | | version 存在 | 批次 failed | | source_dir 存在 | 批次 failed | | templates 非空 | 批次 failed | | code 唯一 | 批次 failed | | source_file 存在 | 对应模板不可用 | | target.type 支持 | 对应字段跳过并记录 | --- ## 八、服务详细设计 ### 8.1 TemplateConfigService ```python def load_template_config() -> dict: """读取 YAML 模板配置。""" def validate_template_config(config: dict) -> list[str]: """返回配置错误列表。""" def compute_config_hash(path: Path) -> str: """计算模板配置 SHA-256。""" ``` ### 8.2 TemplateSelectionService ```python def parse_requested_templates(message: str) -> list[str]: """从用户话语中识别指定模板。""" def detect_registration_type(batch: ApplicationFormFillBatch, message: str) -> tuple[str, str]: """按用户话语、法规核查批次、文件抽取结果识别注册类型及来源。""" def select_templates( config: dict, requested_templates: list[str], registration_type: str, ) -> tuple[list[TemplateSpec], list[dict]]: """输出模板列表和风险提示。""" ``` 注册类型优先级: ```text 用户话语明确指定 -> source_regulatory_batch.condition_json / confirmed_conditions -> source_summary_batch 文件内容抽取候选 -> unknown ``` ### 8.3 TemplateRepository ```python def resolve_source_template(spec: TemplateSpec) -> Path: """返回原始模板路径或预转换工作模板路径。""" def copy_template_to_batch(spec: TemplateSpec, batch: ApplicationFormFillBatch) -> Path: """复制模板到批次 work_dir/templates。""" def convert_doc_to_docx(source: Path, target_dir: Path) -> Path: """P1 能力:使用 soffice 转 docx。""" ``` `.doc` 模板本期处理: | 场景 | 处理 | | --- | --- | | 存在 working_template docx | 使用工作模板 | | 仅有 `.doc` 且无 soffice | 对应模板失败,其他模板继续 | | 具备 soffice | 转换为 `.docx` 后继续 | ### 8.4 FieldExtractionService ```python def collect_document_texts(summary_batch: FileSummaryBatch) -> dict[str, str]: """复用 text_extract 读取文件文本。""" def extract_by_rules(texts: dict[str, str], specs: list[TemplateSpec]) -> dict: """规则/正则抽取字段。""" def extract_by_llm(texts: dict[str, str], specs: list[TemplateSpec]) -> dict: """LLM 结构化抽取字段。""" def run_parallel_extract(texts: dict[str, str], specs: list[TemplateSpec]) -> tuple[dict, dict]: """并行执行规则/正则与 LLM 抽取。""" ``` 并行实现可使用 `ThreadPoolExecutor(max_workers=2)`。LLM 超时或失败时,保留规则/正则结果继续。 ### 8.5 FieldMergeService ```python def normalize_field_value(value: str) -> str: """字段值归一化。""" def rank_source(source_role: str, source_file: str) -> int: """说明书优先,其次产品技术要求、检测报告、性能研究等。""" def merge_fields(regex_results: dict, llm_results: dict) -> tuple[dict[str, MergedField], list[dict]]: """合并字段并输出冲突。""" ``` 来源优先级: | 排名 | 来源 | | --- | --- | | 1 | 说明书 | | 2 | 产品技术要求 | | 3 | 注册检验报告/检测报告 | | 4 | 性能研究资料 | | 5 | 其他注册资料 | ### 8.6 WordTemplateFillService ```python def fill_template( template_path: Path, output_path: Path, spec: TemplateSpec, fields: dict[str, MergedField], checklist_items: dict[str, Any], ) -> Path: """填充 Word 模板并保存。""" def fill_table_row(document: Document, row_label: str, value: str, conflict: bool) -> bool: """根据表格行首字段名定位并填入第二列。""" def replace_placeholders(document: Document, fields: dict[str, MergedField]) -> None: """替换段落中的 {{field_key}}。""" def apply_conflict_style(cell_or_run) -> None: """应用黄色底色和红色字体。""" ``` 冲突样式: | 样式 | 说明 | | --- | --- | | 字体颜色 | 红色 `FF0000` | | 底色 | 黄色 `FFFF00` | | 适用范围 | 单元格或字段值 run | ### 8.7 TraceabilityExportService ```python def build_traceability_workbook(batch, merged_fields, conflicts, specs) -> Workbook: """生成追溯清单 Excel。""" def save_traceability_excel(batch, workbook) -> ExportedSummaryFile: """保存 Excel 并写导出记录。""" def save_extract_json(batch, payload: dict) -> ApplicationFormFillArtifact: """保存字段抽取 JSON 过程产物。""" ``` Excel Sheet: | Sheet | 内容 | | --- | --- | | 字段追溯 | 模板、字段、填入值、来源文件、证据、冲突状态 | | 冲突字段 | 字段、采用值、冲突值、处理方式 | | 低置信度条目 | 安全和性能基本原则清单候选判断 | | 生成结果 | 模板文件、Word 状态、PDF 状态、错误说明 | ### 8.8 FormFillNotifier ```python def notify_completion(batch: ApplicationFormFillBatch, exports: list[ExportedSummaryFile]) -> ApplicationFormFillNotificationRecord: """发送填表完成通知。""" ``` 通知摘要包含: | 内容 | 说明 | | --- | --- | | 批次号 | 填表批次 | | 产品名称 | 如已识别 | | 生成模板 | 模板名称列表 | | 冲突数量 | 提示需下载核对 | | 下载提示 | 提示回到系统对话下载,不直接暴露敏感全文 | --- ## 九、工作流执行器详细设计 ### 9.1 启动入口 ```python def start_application_form_fill_workflow(batch: ApplicationFormFillBatch, *, async_run: bool = True) -> None: executor = FormFillWorkflowExecutor(batch) if async_run: Thread(target=executor.run, daemon=True).start() else: executor.run() ``` ### 9.2 执行伪代码 ```python class FormFillWorkflowExecutor: def run(self) -> None: self.mark_batch_running() try: for node in self.nodes(): if node.status == "success": continue self.run_node(node) self.complete_or_partial() except WorkflowPausedForUser: self.mark_waiting_user() except Exception as exc: self.mark_failed(exc) ``` ### 9.3 节点处理要点 | 节点 | 处理 | | --- | --- | | prepare | 校验 `source_summary_batch` 成功且属于当前对话 | | template_select | 读取 YAML、识别注册类型、选择模板 | | template_copy | 复制模板到 `work_dir/templates` | | field_extract | 抽取文本,规则/正则与 LLM 并行,保存 JSON | | conflict_merge | 合并字段,写 `conflict_summary` | | word_fill | 逐模板生成 Word,写 `ExportedSummaryFile(word)` | | pdf_convert | 本期 skipped,写 `risk_notes` | | trace_export | 生成追溯 Excel 和 JSON | | output_export | 生成 AI 对话 Markdown 摘要 | | notify | 写飞书通知记录,失败不阻断 | | completed | 标记 success 或 partial_success | ### 9.4 批次状态决策 | 条件 | 状态 | | --- | --- | | 所有目标 Word 均成功,追溯清单成功,通知成功或跳过 | success | | 至少一个 Word 成功,但部分模板、追溯清单、PDF 或通知失败 | partial_success | | 所有目标 Word 均失败 | failed | | 无来源文件汇总批次 | waiting_user | --- ## 十、接口详细设计 ### 10.1 发起自动填表 ```text POST /api/review-agent/application-form-fill/start/ ``` 请求: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | conversation_id | integer | 是 | 当前对话 | | message_id | integer | 否 | 触发消息 | | file_summary_batch_id | integer | 否 | 指定文件来源批次 | | template_codes | array | 否 | 指定模板 | | output_types | array | 否 | 输出类型,默认 word、excel、json | 响应: ```json { "batch_id": 3001, "workflow_type": "application_form_fill", "status": "pending", "selected_templates": ["registration_certificate", "essential_principles"] } ``` ### 10.2 查询状态 ```text GET /api/review-agent/application-form-fill/{batch_id}/ ``` 响应: ```json { "batch": { "id": 3001, "batch_no": "AFF-20260607153000-a1b2c3", "status": "success", "product_name": "甲胎蛋白检测试剂盒", "selected_templates": ["registration_certificate"] }, "nodes": [], "conflicts": [], "exports": [] } ``` ### 10.3 下载文件 继续复用既有导出下载接口: ```text GET /api/review-agent/file-summary/exports/{export_id}/download/ ``` 下载权限通过 `workflow_type=application_form_fill` 和 `workflow_batch_id` 反查填表批次。 --- ## 十一、前端详细设计 ### 11.1 工作流卡片 新增卡片类型 `application_form_fill`。 | 节点 | 展示 | | --- | --- | | prepare | 准备资料 | | template_select | 选择模板 | | template_copy | 复制模板 | | field_extract | 抽取字段 | | conflict_merge | 冲突归并 | | word_fill | 填写 Word | | pdf_convert | 转换 PDF | | trace_export | 追溯清单 | | output_export | 输出下载 | | notify | 飞书通知 | | completed | 已完成 | PDF 本期显示为“已跳过/待增强”,不显示为失败。 ### 11.2 AI 回复摘要 ```markdown 已生成申报模板自动填表文件。 | 文件 | Word | PDF | | --- | --- | --- | | 注册证格式 | 下载 | 待增强 | | 安全和性能基本原则清单 | 下载 | 待增强 | | 冲突字段 | 采用值 | 冲突来源 | 处理 | | --- | --- | --- | --- | | 储存条件 | 2-8℃保存 | 产品技术要求:-20℃保存 | 已按说明书填入,并在模板中高亮 | [下载字段来源追溯清单](download-url) ``` --- ## 十二、异常与降级 | 场景 | 处理 | | --- | --- | | 无成功汇总批次 | 批次 waiting_user,对话提示上传资料 | | 模板配置不存在 | 批次 failed | | 指定模板不存在 | 忽略无效模板并提示;若无有效模板则 failed | | `.doc` 模板无可用工作模板 | 该模板失败,其他模板继续 | | 文本抽取失败 | 对应文件跳过,记录在追溯清单 | | LLM 抽取失败 | 使用规则/正则结果继续 | | 字段缺失 | Word 留空 | | 字段冲突 | 说明书优先并高亮 | | 追溯清单失败 | Word 成功时批次 partial_success | | 飞书通知失败 | 批次 partial_success 或 success,取决于核心产物是否成功 | | PDF 未实现 | 节点 skipped,写入待增强提示 | --- ## 十三、测试设计 ### 13.1 单元测试 | 用例 | 目标 | | --- | --- | | test_form_fill_trigger_keywords | 触发语句识别为自动填表 | | test_template_config_loads | YAML 配置可加载并校验 | | test_select_default_templates_initial_registration | 首次注册默认选择注册证和基本原则清单 | | test_select_user_requested_mismatch | 用户指定不适用模板仍允许生成并提示 | | test_field_merge_prefers_instructions | 说明书字段优先 | | test_field_merge_marks_conflict | 冲突字段进入 conflict_summary | | test_word_fill_table_row | 能按表格行名写入 Word | | test_word_fill_conflict_highlight | 冲突字段黄底红字 | | test_traceability_excel | 追溯清单包含字段、来源和冲突 | | test_notify_records_failure | 飞书失败写通知记录但不阻断 | ### 13.2 集成测试 | 场景 | 验证 | | --- | --- | | 最近汇总批次触发填表 | 无附件时复用最近 success `FileSummaryBatch` | | 新附件触发填表 | 先自动汇总再启动填表 | | 注册证模板填充 | 生成 Word 导出文件 | | LLM 失败降级 | LLM 超时后规则抽取仍可生成 Word | | 部分模板失败 | 至少一个 Word 成功时批次 partial_success | | 权限隔离 | 不能查询或下载他人填表批次产物 | ### 13.3 前端验证 | 场景 | 验证 | | --- | --- | | 自动填表卡片 | 节点状态随 SSE 更新 | | 指定模板展示 | 卡片展示本次选择模板 | | PDF 跳过显示 | PDF 节点显示待增强而非失败 | | 下载链接 | Word 和追溯清单链接可点击下载 | | 冲突摘要 | 冲突字段表格正常渲染 | --- ## 十四、实施顺序建议 1. 修改功能设计中的模板配置路径为 `review_agent/application_form_fill/templates/application_form_templates_v1.yaml`。 2. 新增数据库模型和 `ExportedSummaryFile.ExportType` 扩展。 3. 新增 `application_form_fill` 模块目录和常量、schemas、storage。 4. 新增模板配置 YAML,先录入注册证 `.docx` 的已识别字段。 5. 实现模板选择、模板复制和 Word 表格行填充。 6. 实现规则/正则字段抽取和 LLM 抽取降级。 7. 实现字段合并、冲突高亮和追溯清单。 8. 实现工作流执行器、节点事件和状态接口。 9. 改造路由和前端工作流卡片。 10. 接入飞书通知记录。 11. 将字段级数据库表和 PDF 转换写入待办计划。