feat: 持久化资料包导出记录与列表摘要

This commit is contained in:
2026-06-04 02:41:49 +08:00
parent 0e49466746
commit ab3d520642
9 changed files with 250 additions and 8 deletions

View File

@@ -8,6 +8,7 @@ import zipfile
from django.conf import settings
from agent_core.governance import load_governance_config
from apps.documents.services import create_export_record
def generate_registration_export(*, batch, conversation, upstream_summary: dict | None = None) -> dict:
@@ -48,7 +49,7 @@ def generate_registration_export(*, batch, conversation, upstream_summary: dict
if can_export_formally
else "已生成草稿导出文件,正式版仍被风险项阻断。"
)
return {
report = {
"output_type": "registration_word_export_report",
"summary": summary,
"template_name": template_mapping["template_name"],
@@ -72,6 +73,19 @@ def generate_registration_export(*, batch, conversation, upstream_summary: dict
"export_mode": export_mode,
},
}
create_export_record(
batch=batch,
conversation_id=conversation.conversation_id,
product_name=batch.product_name or conversation.product_name,
template_name=report["template_name"],
template_version=report["template_version"],
export_mode=export_mode,
output_type=report["output_type"],
file_name=file_name,
relative_path=relative_path.as_posix(),
download_url=download_url,
)
return report
def update_conversation_with_export_report(conversation, export_report: dict) -> None:

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from .models import UploadedDocument
from .models import ExportedDocument, UploadedDocument
@admin.register(UploadedDocument)
@@ -9,3 +9,19 @@ class UploadedDocumentAdmin(admin.ModelAdmin):
list_display = ("id", "original_name", "scenario_id", "file_type", "status", "created_at")
list_filter = ("status", "scenario_id", "file_type")
search_fields = ("original_name", "scenario_id")
@admin.register(ExportedDocument)
class ExportedDocumentAdmin(admin.ModelAdmin):
"""管理导出记录,便于按批次、会话和产品回看导出产物。"""
list_display = (
"id",
"file_name",
"batch",
"conversation_id",
"product_name",
"export_mode",
"created_at",
)
list_filter = ("export_mode", "output_type", "template_name")
search_fields = ("file_name", "batch__batch_id", "conversation_id", "product_name")

View File

@@ -0,0 +1,52 @@
# Generated by Django 5.2.14 on 2026-06-04 18:20
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("documents", "0002_submissionbatch_uploadeddocument_chapter_code_and_more"),
]
operations = [
migrations.CreateModel(
name="ExportedDocument",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("conversation_id", models.CharField(db_index=True, max_length=64)),
("product_name", models.CharField(blank=True, db_index=True, max_length=255)),
("template_name", models.CharField(blank=True, max_length=100)),
("template_version", models.CharField(blank=True, max_length=50)),
("export_mode", models.CharField(db_index=True, max_length=32)),
(
"output_type",
models.CharField(default="registration_word_export_report", max_length=100),
),
("file_name", models.CharField(max_length=255)),
("relative_path", models.CharField(max_length=500)),
("download_url", models.CharField(blank=True, max_length=500)),
("created_at", models.DateTimeField(auto_now_add=True, db_index=True)),
(
"batch",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="export_records",
to="documents.submissionbatch",
),
),
],
options={
"ordering": ["-created_at"],
},
),
]

View File

@@ -92,3 +92,36 @@ class UploadedDocument(models.Model):
self.STATUS_INDEXED: "已入库,可检索",
self.STATUS_FAILED: "入库失败",
}.get(self.status, self.status)
class ExportedDocument(models.Model):
"""
导出文件记录。
该对象属于资料包治理范围:
- Documents 维护导出产物与资料包关系
- Chat 只负责触发导出动作
- Audit 负责回看执行痕迹
"""
batch = models.ForeignKey(
SubmissionBatch,
related_name="export_records",
on_delete=models.CASCADE,
)
conversation_id = models.CharField(max_length=64, db_index=True)
product_name = models.CharField(max_length=255, blank=True, db_index=True)
template_name = models.CharField(max_length=100, blank=True)
template_version = models.CharField(max_length=50, blank=True)
export_mode = models.CharField(max_length=32, db_index=True)
output_type = models.CharField(max_length=100, default="registration_word_export_report")
file_name = models.CharField(max_length=255)
relative_path = models.CharField(max_length=500)
download_url = models.CharField(max_length=500, blank=True)
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
class Meta:
ordering = ["-created_at"]
def __str__(self) -> str:
return self.file_name

View File

@@ -9,7 +9,7 @@ from agent_core.rag.ingest import ingest_document
from apps.chat.services import create_conversation_for_batch
from django.core.files.uploadedfile import SimpleUploadedFile
from .models import SubmissionBatch, UploadedDocument
from .models import ExportedDocument, SubmissionBatch, UploadedDocument
def create_uploaded_document(
@@ -198,6 +198,55 @@ def append_documents_to_batch(
}
def create_export_record(
*,
batch: SubmissionBatch,
conversation_id: str,
product_name: str,
template_name: str,
template_version: str,
export_mode: str,
output_type: str,
file_name: str,
relative_path: str,
download_url: str,
) -> ExportedDocument:
"""
保存导出文件记录,供资料包与处理历史统一回看。
"""
return ExportedDocument.objects.create(
batch=batch,
conversation_id=conversation_id,
product_name=product_name,
template_name=template_name,
template_version=template_version,
export_mode=export_mode,
output_type=output_type,
file_name=file_name,
relative_path=relative_path,
download_url=download_url,
)
def build_batch_rows(batches) -> list[dict]:
"""
为资料包列表补齐最近导出摘要。
"""
batch_ids = [batch.id for batch in batches]
latest_exports = {}
for record in ExportedDocument.objects.filter(batch_id__in=batch_ids).order_by("batch_id", "-created_at"):
latest_exports.setdefault(record.batch_id, record)
rows = []
for batch in batches:
rows.append(
{
"batch": batch,
"latest_export": latest_exports.get(batch.id),
}
)
return rows
def extract_text(document: UploadedDocument) -> str:
"""
根据文档类型选择合适的文本抽取策略。

View File

@@ -7,7 +7,7 @@ from apps.scenarios.services import list_scenarios
from .forms import DocumentUploadForm
from .models import SubmissionBatch, UploadedDocument
from .services import import_submission_batch, index_document
from .services import build_batch_rows, import_submission_batch, index_document
def document_list(request):
@@ -45,6 +45,7 @@ def document_list(request):
{
"documents": documents,
"batches": batches,
"batch_rows": build_batch_rows(batches),
"keyword": keyword,
"status_counts": status_counts,
"processing_pipeline": processing_pipeline,

View File

@@ -67,11 +67,13 @@
<th>文件数</th>
<th>页数</th>
<th>状态</th>
<th>最近导出</th>
<th>章节点概览</th>
</tr>
</thead>
<tbody>
{% for batch in batches %}
{% for row in batch_rows %}
{% with batch=row.batch latest_export=row.latest_export %}
<tr>
<td class="nowrap">{{ batch.batch_id }}</td>
<td>{{ batch.product_name|default:"未识别产品名称" }}</td>
@@ -89,6 +91,14 @@
{{ batch.get_import_status_display_text }}
</span>
</td>
<td class="cell-min-220">
{% if latest_export %}
<div>{{ latest_export.file_name }}</div>
<div class="muted">{{ latest_export.export_mode }} / {{ latest_export.template_version|default:"-" }}</div>
{% else %}
<span class="muted">暂无导出记录</span>
{% endif %}
</td>
<td class="cell-min-280">
{% if batch.chapter_summary %}
{% for chapter in batch.chapter_summary %}
@@ -99,9 +109,10 @@
{% endif %}
</td>
</tr>
{% endwith %}
{% empty %}
<tr>
<td colspan="7">暂无资料包,请先导入申报资料。</td>
<td colspan="8">暂无资料包,请先导入申报资料。</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -9,7 +9,7 @@ from apps.audit.models import AgentAuditLog
from apps.audit.models import NotificationRecord
from apps.chat.models import Conversation
from apps.chat.services import create_conversation_for_batch
from apps.documents.models import SubmissionBatch, UploadedDocument
from apps.documents.models import ExportedDocument, SubmissionBatch, UploadedDocument
def _create_conversation_with_batch():
@@ -547,3 +547,4 @@ def test_chat_export_word_route_persists_real_download_link(client, db, tmp_path
assert "下载导出文件" in content
assert export_report["download_url"] in content
assert AgentAuditLog.objects.filter(conversation_id=conversation.conversation_id).count() == 1
assert ExportedDocument.objects.filter(batch=batch, conversation_id=conversation.conversation_id).count() == 1

View File

@@ -7,7 +7,7 @@ import types
from zipfile import ZipFile
from apps.documents.forms import DocumentUploadForm
from apps.documents.models import SubmissionBatch, UploadedDocument
from apps.documents.models import ExportedDocument, SubmissionBatch, UploadedDocument
from apps.documents.services import extract_text, import_submission_batch, index_document
from apps.chat.models import Conversation
@@ -475,3 +475,68 @@ def test_import_submission_batch_records_warnings_for_unsupported_7z_entries(db,
assert batch.file_count == 1
assert batch.exception_count == 1
assert any("CH1/忽略图片.png" in warning for warning in warnings)
def test_create_export_record_persists_batch_conversation_and_file_metadata(db):
from apps.documents.services import create_export_record
batch = SubmissionBatch.objects.create(
batch_id="SUB-20260604-010",
product_name="产品X",
workflow_type="registration",
conversation_id="conv-010",
file_count=2,
page_count=12,
import_status="completed",
)
record = create_export_record(
batch=batch,
conversation_id="conv-010",
product_name="产品X",
template_name="注册证导出模板",
template_version="V1.0",
export_mode="draft",
output_type="registration_word_export_report",
file_name="SUB-20260604-010-draft.docx",
relative_path="exports/20260604/SUB-20260604-010-draft.docx",
download_url="/media/exports/20260604/SUB-20260604-010-draft.docx",
)
assert ExportedDocument.objects.count() == 1
assert record.batch == batch
assert record.conversation_id == "conv-010"
assert record.product_name == "产品X"
assert record.template_name == "注册证导出模板"
assert record.export_mode == "draft"
def test_document_list_shows_latest_export_record_for_batch(client, db):
batch = SubmissionBatch.objects.create(
batch_id="SUB-20260604-011",
product_name="产品Y",
workflow_type="registration",
conversation_id="conv-011",
file_count=2,
page_count=12,
import_status="completed",
)
ExportedDocument.objects.create(
batch=batch,
conversation_id="conv-011",
product_name="产品Y",
template_name="注册证导出模板",
template_version="V1.0",
export_mode="draft",
output_type="registration_word_export_report",
file_name="SUB-20260604-011-draft.docx",
relative_path="exports/20260604/SUB-20260604-011-draft.docx",
download_url="/media/exports/20260604/SUB-20260604-011-draft.docx",
)
response = client.get(reverse("documents:list"))
content = response.content.decode("utf-8")
assert response.status_code == 200
assert "最近导出" in content
assert "SUB-20260604-011-draft.docx" in content