feat(application-form-fill): 生成填表 Word 和追溯清单

This commit is contained in:
2026-06-07 18:33:59 +08:00
parent a48f778e09
commit f35a3ba9b4
4 changed files with 462 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
import json
import pytest
from openpyxl import load_workbook
from review_agent.application_form_fill.schemas import MergedField, TemplateSpec
from review_agent.application_form_fill.services.traceability_export import save_traceability_exports
from review_agent.models import (
ApplicationFormFillArtifact,
ApplicationFormFillBatch,
Conversation,
ExportedSummaryFile,
FileSummaryBatch,
)
pytestmark = pytest.mark.django_db
def test_traceability_exports_excel_json_and_records(settings, tmp_path, django_user_model):
settings.MEDIA_ROOT = tmp_path
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="会话")
summary = FileSummaryBatch.objects.create(conversation=conversation, user=user, batch_no="FS-TRACE")
batch = ApplicationFormFillBatch.objects.create(
conversation=conversation,
user=user,
source_summary_batch=summary,
batch_no="AFF-TRACE",
work_dir=str(tmp_path / "aff" / "AFF-TRACE"),
)
spec = TemplateSpec(
code="registration_certificate",
name="注册证格式",
source_file="template.docx",
output_label="注册证格式",
applies_when={},
file_format="docx",
fields=[{"key": "product_name", "label": "产品名称"}],
)
merged_fields = {
"product_name": MergedField(
"product_name",
"产品名称",
"甲胎蛋白检测试剂盒",
"说明书.txt",
"产品名称:甲胎蛋白检测试剂盒",
0.8,
)
}
conflicts = [
{
"field_key": "storage_condition",
"field_label": "储存条件",
"selected_value": "2-8℃",
"conflict_values": [{"value": "-20℃", "source_file": "产品技术要求.txt"}],
"handling": "说明书优先",
}
]
exports = save_traceability_exports(
batch,
merged_fields,
conflicts,
[spec],
[{"template_label": "注册证格式", "word_status": "success", "pdf_status": "待增强"}],
)
assert {export.export_type for export in exports} == {
ExportedSummaryFile.ExportType.EXCEL,
ExportedSummaryFile.ExportType.JSON,
}
excel_export = next(export for export in exports if export.export_type == ExportedSummaryFile.ExportType.EXCEL)
workbook = load_workbook(excel_export.storage_path)
assert workbook.sheetnames == ["字段追溯", "冲突字段", "低置信度条目", "生成结果"]
assert workbook["字段追溯"]["B2"].value == "产品名称"
assert workbook["冲突字段"]["C2"].value == "-20℃"
json_export = next(export for export in exports if export.export_type == ExportedSummaryFile.ExportType.JSON)
payload = json.loads(open(json_export.storage_path, encoding="utf-8").read())
assert payload["merged_fields"]["product_name"]["value"] == "甲胎蛋白检测试剂盒"
assert ApplicationFormFillArtifact.objects.filter(
batch=batch,
artifact_type=ApplicationFormFillArtifact.ArtifactType.TRACEABILITY,
).exists()

View File

@@ -0,0 +1,121 @@
import zipfile
import pytest
from docx import Document
from review_agent.application_form_fill.schemas import MergedField, TemplateSpec
from review_agent.application_form_fill.services.word_fill import create_word_export, fill_template
from review_agent.models import (
ApplicationFormFillArtifact,
ApplicationFormFillBatch,
Conversation,
ExportedSummaryFile,
FileSummaryBatch,
)
pytestmark = pytest.mark.django_db
def _spec():
return TemplateSpec(
code="registration_certificate",
name="注册证格式",
source_file="template.docx",
output_label="注册证格式",
applies_when={"registration_type": ["首次注册"]},
file_format="docx",
fields=[
{"key": "product_name", "label": "产品名称", "target": {"type": "table_row", "row_label": "产品名称"}},
{"key": "intended_use", "label": "预期用途", "target": {"type": "table_row", "row_label": "预期用途"}},
],
)
def _template(path):
document = Document()
table = document.add_table(rows=2, cols=2)
table.rows[0].cells[0].text = "产品名称"
table.rows[1].cells[0].text = "预期用途"
document.save(path)
def test_word_fill_writes_table_rows(tmp_path):
template_path = tmp_path / "template.docx"
output_path = tmp_path / "filled.docx"
_template(template_path)
fill_template(
template_path,
output_path,
_spec(),
{
"product_name": MergedField("product_name", "产品名称", "甲胎蛋白检测试剂盒", "说明书.txt", "证据", 0.8),
"intended_use": MergedField("intended_use", "预期用途", "用于体外检测", "说明书.txt", "证据", 0.8),
},
)
document = Document(output_path)
assert document.tables[0].rows[0].cells[1].text == "甲胎蛋白检测试剂盒"
assert document.tables[0].rows[1].cells[1].text == "用于体外检测"
def test_word_fill_highlights_conflict_in_docx_xml(tmp_path):
template_path = tmp_path / "template.docx"
output_path = tmp_path / "filled.docx"
_template(template_path)
fill_template(
template_path,
output_path,
_spec(),
{
"product_name": MergedField(
"product_name",
"产品名称",
"甲胎蛋白检测试剂盒",
"说明书.txt",
"证据",
0.8,
has_conflict=True,
)
},
conflicts=[{"field_key": "product_name"}],
)
with zipfile.ZipFile(output_path) as package:
document_xml = package.read("word/document.xml").decode("utf-8")
assert 'w:fill="FFFF00"' in document_xml
assert 'w:color w:val="FF0000"' in document_xml
def test_create_word_export_records_artifact_and_export(settings, tmp_path, django_user_model):
settings.MEDIA_ROOT = tmp_path
user = django_user_model.objects.create_user(username="owner", password="pass")
conversation = Conversation.objects.create(user=user, title="会话")
summary = FileSummaryBatch.objects.create(conversation=conversation, user=user, batch_no="FS-WORD")
batch = ApplicationFormFillBatch.objects.create(
conversation=conversation,
user=user,
source_summary_batch=summary,
batch_no="AFF-WORD",
product_name="甲胎蛋白检测试剂盒",
work_dir=str(tmp_path / "aff" / "AFF-WORD"),
)
template_path = tmp_path / "template.docx"
_template(template_path)
exported = create_word_export(
batch,
_spec(),
template_path,
{"product_name": MergedField("product_name", "产品名称", "甲胎蛋白检测试剂盒", "说明书.txt", "证据", 0.8)},
)
assert exported.export_type == ExportedSummaryFile.ExportType.WORD
assert exported.workflow_type == "application_form_fill"
assert exported.workflow_batch_id == batch.pk
assert ApplicationFormFillArtifact.objects.filter(
batch=batch,
artifact_type=ApplicationFormFillArtifact.ArtifactType.FILLED_TEMPLATE,
).exists()