feat(application-form-fill): 展示自动填表工作流卡片

This commit is contained in:
2026-06-07 18:43:39 +08:00
parent 9be10ef990
commit 4ac9c04dbf
7 changed files with 335 additions and 5 deletions

View File

@@ -1,7 +1,127 @@
from django.http import JsonResponse
import json
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.http import Http404, JsonResponse
from django.views.decorators.http import require_http_methods
from review_agent.application_form_fill.workflow import (
create_application_form_fill_batch,
find_latest_successful_summary_batch,
start_application_form_fill_workflow,
)
from review_agent.models import ApplicationFormFillBatch, Conversation, ExportedSummaryFile, FileSummaryBatch, WorkflowNodeRun
@require_http_methods(["GET"])
def health(request):
return JsonResponse({"workflow_type": "application_form_fill", "status": "available"})
@login_required
@require_http_methods(["POST"])
def start(request):
try:
payload = json.loads(request.body.decode("utf-8") or "{}")
except json.JSONDecodeError:
return JsonResponse({"error": "JSON 格式错误。"}, status=400)
conversation = Conversation.objects.filter(pk=payload.get("conversation_id"), user=request.user).first()
if not conversation:
raise Http404("对话不存在。")
summary_batch = None
if payload.get("file_summary_batch_id"):
summary_batch = FileSummaryBatch.objects.filter(
pk=payload.get("file_summary_batch_id"),
conversation=conversation,
user=request.user,
status=FileSummaryBatch.Status.SUCCESS,
).first()
if summary_batch is None:
summary_batch = find_latest_successful_summary_batch(conversation)
if summary_batch is None:
return JsonResponse({"error": "请先上传资料并完成文件汇总。"}, status=400)
batch = create_application_form_fill_batch(
conversation=conversation,
user=request.user,
source_summary_batch=summary_batch,
requested_templates=payload.get("template_codes") or [],
output_types=payload.get("output_types") or None,
)
start_application_form_fill_workflow(batch, async_run=getattr(settings, "APPLICATION_FORM_FILL_ASYNC", True))
return JsonResponse(
{
"batch_id": batch.pk,
"workflow_type": "application_form_fill",
"status": batch.status,
"selected_templates": batch.selected_templates,
}
)
@login_required
@require_http_methods(["GET"])
def batch_status(request, batch_id: int):
batch = ApplicationFormFillBatch.objects.filter(
pk=batch_id,
conversation__user=request.user,
is_deleted=False,
).first()
if not batch:
raise Http404("填表批次不存在。")
exports = ExportedSummaryFile.objects.filter(
workflow_type="application_form_fill",
workflow_batch_id=batch.pk,
).order_by("id")
return JsonResponse(
{
"batch": {
"id": batch.pk,
"workflow_type": "application_form_fill",
"batch_no": batch.batch_no,
"status": batch.status,
"product_name": batch.product_name,
"selected_templates": batch.selected_templates,
"conflict_count": len(batch.conflict_summary or []),
"risk_summary_text": _risk_summary_text(batch),
"error_message": batch.error_message,
},
"nodes": [
{
"node_code": node.node_code,
"node_name": node.node_name,
"status": node.status,
"progress": node.progress,
"message": node.message,
}
for node in WorkflowNodeRun.objects.filter(
workflow_type="application_form_fill",
workflow_batch_id=batch.pk,
).order_by("id")
],
"conflicts": batch.conflict_summary or [],
"exports": [
{
"id": export.pk,
"export_type": export.export_type,
"export_category": export.export_category,
"file_name": export.file_name,
"download_url": f"/api/review-agent/file-summary/exports/{export.pk}/download/",
}
for export in exports
],
}
)
def _risk_summary_text(batch: ApplicationFormFillBatch) -> str:
parts = []
if batch.selected_templates:
parts.append("模板 " + "".join(batch.selected_templates))
if batch.conflict_summary:
parts.append(f"冲突字段 {len(batch.conflict_summary)}")
if batch.risk_notes:
parts.append(f"提示 {len(batch.risk_notes)}")
return " · ".join(parts)

View File

@@ -16,6 +16,10 @@ from .regulatory_review.views import (
review_issues as regulatory_review_review_issues,
start_full_review as regulatory_review_start_full_review,
)
from .application_form_fill.views import (
batch_status as application_form_fill_batch_status,
start as application_form_fill_start,
)
urlpatterns = [
@@ -84,4 +88,14 @@ urlpatterns = [
regulatory_review_review_issues,
name="regulatory_review_review_issues",
),
path(
"api/review-agent/application-form-fill/start/",
application_form_fill_start,
name="application_form_fill_start",
),
path(
"api/review-agent/application-form-fill/<int:batch_id>/status/",
application_form_fill_batch_status,
name="application_form_fill_batch_status",
),
]

View File

@@ -11,7 +11,7 @@ from .services import (
send_message,
stream_message,
)
from .models import Conversation, FileAttachment, FileSummaryBatch, RegulatoryReviewBatch, WorkflowNodeRun
from .models import ApplicationFormFillBatch, Conversation, FileAttachment, FileSummaryBatch, RegulatoryReviewBatch, WorkflowNodeRun
from .regulatory_review.services.info_extract import ensure_regulatory_condition_candidates
@@ -155,6 +155,25 @@ def build_workflow_cards(conversation: Conversation) -> list[dict[str, object]]:
),
}
)
form_fill_batches = ApplicationFormFillBatch.objects.filter(conversation=conversation, is_deleted=False)
for batch in form_fill_batches:
cards.append(
{
"id": batch.pk,
"workflow_type": "application_form_fill",
"batch_no": batch.batch_no,
"status": batch.status,
"error_message": batch.error_message,
"risk_label": _format_form_fill_label(batch),
"created_at": batch.created_at,
"nodes": list(
WorkflowNodeRun.objects.filter(
workflow_type="application_form_fill",
workflow_batch_id=batch.pk,
).order_by("id")
),
}
)
return sorted(cards, key=lambda item: item["created_at"], reverse=True)[:5]
@@ -187,3 +206,14 @@ def _format_risk_label(risk_summary: dict) -> str:
if count:
parts.append(f"{label} {count}")
return " · ".join(parts)
def _format_form_fill_label(batch: ApplicationFormFillBatch) -> str:
parts = []
if batch.selected_templates:
parts.append("模板 " + "".join(batch.selected_templates))
if batch.conflict_summary:
parts.append(f"冲突字段 {len(batch.conflict_summary)}")
if batch.risk_notes:
parts.append(f"提示 {len(batch.risk_notes)}")
return " · ".join(parts)