From 4e46f27c287018bc8f9a86193126fe2577dd3865 Mon Sep 17 00:00:00 2001 From: bruce Date: Sun, 7 Jun 2026 09:40:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(regulatory):=20=E5=AE=8C=E5=96=84=E5=A4=8D?= =?UTF-8?q?=E6=A0=B8=E5=89=8D=E7=AB=AF=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- review_agent/regulatory_review/views.py | 42 +++++++++++----------- review_agent/views.py | 2 ++ static/js/app.js | 23 ++++++++++++ templates/home.html | 17 +++++++++ tests/test_regulatory_frontend.py | 47 +++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 21 deletions(-) diff --git a/review_agent/regulatory_review/views.py b/review_agent/regulatory_review/views.py index e4206a8..86c8dcd 100644 --- a/review_agent/regulatory_review/views.py +++ b/review_agent/regulatory_review/views.py @@ -79,6 +79,27 @@ def confirm_conditions(request, batch_id: int): progress=100, message="适用条件已确认", ) + record_event( + batch, + "condition_confirmed", + {"conditions": batch.condition_json["confirmed_conditions"], "resume_from": "rule_scope"}, + ) + start_regulatory_review_workflow( + batch, + async_run=getattr(settings, "REGULATORY_REVIEW_ASYNC", True), + ) + batch.refresh_from_db() + return JsonResponse( + { + "batch": { + "id": batch.pk, + "workflow_type": "regulatory_review", + "batch_no": batch.batch_no, + "status": batch.status, + "condition_json": batch.condition_json, + } + } + ) @require_http_methods(["POST"]) @@ -160,27 +181,6 @@ def review_issues(request, batch_id: int): return JsonResponse({"error": "file_summary_batch_id 不存在或未成功。"}, status=400) record = review_missing_issues(batch=batch, issue_ids=[int(item) for item in issue_ids], file_summary_batch=summary_batch) return JsonResponse({"review_record": record}) - record_event( - batch, - "condition_confirmed", - {"conditions": batch.condition_json["confirmed_conditions"], "resume_from": "rule_scope"}, - ) - start_regulatory_review_workflow( - batch, - async_run=getattr(settings, "REGULATORY_REVIEW_ASYNC", True), - ) - batch.refresh_from_db() - return JsonResponse( - { - "batch": { - "id": batch.pk, - "workflow_type": "regulatory_review", - "batch_no": batch.batch_no, - "status": batch.status, - "condition_json": batch.condition_json, - } - } - ) def _format_risk_summary(risk_summary: dict) -> str: diff --git a/review_agent/views.py b/review_agent/views.py index a6be16f..429715e 100644 --- a/review_agent/views.py +++ b/review_agent/views.py @@ -140,6 +140,8 @@ def build_workflow_cards(conversation: Conversation) -> list[dict[str, object]]: "risk_label": _format_risk_label(batch.risk_summary or {}), "condition_json": batch.condition_json or {}, "condition_candidates": (batch.condition_json or {}).get("candidates") or {}, + "notification_count": batch.notifications.count(), + "review_record_count": batch.artifacts.filter(metadata__artifact="review_record").count(), "created_at": batch.created_at, "nodes": list( WorkflowNodeRun.objects.filter( diff --git a/static/js/app.js b/static/js/app.js index b41d8a5..0717538 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -850,6 +850,28 @@ }); } + function bindRectificationActionButtons() { + document.querySelectorAll("[data-rectification-action]").forEach(function (button) { + if (button.dataset.bound === "true") { + return; + } + button.dataset.bound = "true"; + button.addEventListener("click", function () { + if (!promptInput) { + return; + } + var action = button.getAttribute("data-rectification-action"); + var batchNo = button.getAttribute("data-batch-no") || ""; + if (action === "full-review") { + promptInput.value = "请基于新的文件汇总批次,对法规核查批次 " + batchNo + " 发起整包复核,并先确认使用哪个补充批次。"; + } else { + promptInput.value = "请对法规核查批次 " + batchNo + " 的缺失项发起复核,并先确认 issue_ids 和补充文件汇总批次。"; + } + promptInput.focus(); + }); + }); + } + async function streamChat(event) { event.preventDefault(); if (!composer || !promptInput || !sendButton || !chatStage) { @@ -1010,6 +1032,7 @@ refreshWorkflowBatchCarousel(0); bindWorkflowBatchCarouselControls(); bindConditionConfirmForms(); + bindRectificationActionButtons(); refreshRunningWorkflowCards(); if (chatScroll) { diff --git a/templates/home.html b/templates/home.html index 98a03e8..f8ee602 100644 --- a/templates/home.html +++ b/templates/home.html @@ -237,6 +237,23 @@ {% if batch.risk_label %}

{{ batch.risk_label }}

{% endif %} + {% if batch.workflow_type == "regulatory_review" %} +
+ + +
+

+ 通知 {{ batch.notification_count|default:0 }} · 复核记录 {{ batch.review_record_count|default:0 }} +

+ {% endif %} {% if batch.error_message %}

{{ batch.error_message }}

{% endif %} diff --git a/tests/test_regulatory_frontend.py b/tests/test_regulatory_frontend.py index f9a21d0..1fac3e7 100644 --- a/tests/test_regulatory_frontend.py +++ b/tests/test_regulatory_frontend.py @@ -4,6 +4,8 @@ from django.urls import reverse from review_agent.models import ( Conversation, FileSummaryBatch, + RegulatoryArtifact, + RegulatoryNotificationRecord, RegulatoryReviewBatch, WorkflowNodeRun, ) @@ -102,6 +104,49 @@ def test_workspace_renders_condition_confirmation_form(client, django_user_model assert "甲胎蛋白检测试剂盒" in content +def test_workspace_renders_rectification_actions_and_summaries(client, tmp_path, django_user_model): + 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-OK", + status=FileSummaryBatch.Status.SUCCESS, + ) + regulatory = RegulatoryReviewBatch.objects.create( + conversation=conversation, + user=user, + source_summary_batch=summary, + batch_no="RR-RECTIFY", + status=RegulatoryReviewBatch.Status.SUCCESS, + ) + record_path = tmp_path / "review_record.json" + record_path.write_text('{"items":[{"status":"review_passed"}]}', encoding="utf-8") + RegulatoryArtifact.objects.create( + batch=regulatory, + artifact_type=RegulatoryArtifact.ArtifactType.JSON, + name="review_record.json", + storage_path=str(record_path), + metadata={"artifact": "review_record"}, + ) + RegulatoryNotificationRecord.objects.create( + batch=regulatory, + channel=RegulatoryNotificationRecord.Channel.MOCK, + target="法规整改负责人", + status=RegulatoryNotificationRecord.Status.SENT, + payload={"title": "缺少申请表"}, + ) + client.force_login(user) + + response = client.get(f"{reverse('home')}?conversation={conversation.pk}") + + content = response.content.decode("utf-8") + assert "data-rectification-action=\"full-review\"" in content + assert "data-rectification-action=\"issue-review\"" in content + assert "通知 1" in content + assert "复核记录 1" in content + + def test_frontend_selects_status_url_by_workflow_type(): script = open("static/js/app.js", encoding="utf-8").read() @@ -110,3 +155,5 @@ def test_frontend_selects_status_url_by_workflow_type(): assert "statusUrlForWorkflow" in script assert "bindConditionConfirmForms" in script assert "data-condition-confirm-form" in script + assert "bindRectificationActionButtons" in script + assert "data-rectification-action" in script