diff --git a/apps/audit/services.py b/apps/audit/services.py
index 7a0ef0e..93b4ed4 100644
--- a/apps/audit/services.py
+++ b/apps/audit/services.py
@@ -94,3 +94,29 @@ def create_notification_record(
web_detail_url=web_detail_url,
receipt=receipt,
)
+
+
+def build_history_rows(logs) -> list[dict]:
+ """
+ 为处理历史列表补齐风险状态和通知状态。
+
+ View 只负责收集筛选条件,列表展示所需的聚合字段统一在服务层完成。
+ """
+ notification_map = {
+ (item.batch_id, item.conversation_id): item
+ for item in NotificationRecord.objects.order_by("-created_at")
+ }
+ rows = []
+ for log in logs:
+ notification = notification_map.get((log.batch_id, log.conversation_id))
+ structured_output = log.structured_output or {}
+ rows.append(
+ {
+ "log": log,
+ "risk_status": structured_output.get("highest_risk_level")
+ or structured_output.get("risk_level")
+ or "-",
+ "notify_status": notification.message_status if notification else "-",
+ }
+ )
+ return rows
diff --git a/apps/audit/views.py b/apps/audit/views.py
index 383720f..294ff1e 100644
--- a/apps/audit/views.py
+++ b/apps/audit/views.py
@@ -2,24 +2,39 @@ from django.shortcuts import get_object_or_404, render
from .models import AgentAuditLog, NotificationRecord
from apps.chat.models import Conversation
+from .services import build_history_rows
def log_list(request):
# 处理历史页支持按批次、产品和状态筛选。
scenario_id = (request.GET.get("scenario_id") or "").strip()
keyword = (request.GET.get("keyword") or "").strip()
+ notify_status = (request.GET.get("notify_status") or "").strip()
logs = AgentAuditLog.objects.all()
if scenario_id:
logs = logs.filter(scenario_id=scenario_id)
if keyword:
logs = logs.filter(product_name__icontains=keyword) | logs.filter(batch_id__icontains=keyword)
+ if notify_status:
+ matched_pairs = list(
+ NotificationRecord.objects.filter(message_status=notify_status).values_list(
+ "batch_id",
+ "conversation_id",
+ )
+ )
+ logs = [
+ log
+ for log in logs
+ if (log.batch_id, log.conversation_id) in matched_pairs
+ ]
return render(
request,
"audit/log_list.html",
{
- "logs": logs,
+ "history_rows": build_history_rows(logs),
"selected_scenario_id": scenario_id,
"keyword": keyword,
+ "notify_status": notify_status,
},
)
diff --git a/templates/audit/log_list.html b/templates/audit/log_list.html
index 85fd354..ecdbda6 100644
--- a/templates/audit/log_list.html
+++ b/templates/audit/log_list.html
@@ -21,7 +21,15 @@
-
+
+
+
+
+
@@ -46,29 +54,33 @@
会话 |
输入摘要 |
状态 |
+
风险状态 |
+
通知状态 |
模型 |
时间 |
详情 |
- {% for log in logs %}
+ {% for row in history_rows %}
- | {{ log.id }} |
- {{ log.scenario_name }} |
- {{ log.product_name|default:"-" }} |
- {{ log.batch_id|default:"-" }} |
- {{ log.conversation_id|default:"-" }} |
- {{ log.get_user_input_summary }} |
+ {{ row.log.id }} |
+ {{ row.log.scenario_name }} |
+ {{ row.log.product_name|default:"-" }} |
+ {{ row.log.batch_id|default:"-" }} |
+ {{ row.log.conversation_id|default:"-" }} |
+ {{ row.log.get_user_input_summary }} |
- {{ log.get_status_display_text }}
+ {{ row.log.get_status_display_text }}
|
- {{ log.model_name }} |
- {{ log.created_at|date:"Y-m-d H:i" }} |
- 查看详情 |
+ {{ row.risk_status }} |
+ {{ row.notify_status }} |
+ {{ row.log.model_name }} |
+ {{ row.log.created_at|date:"Y-m-d H:i" }} |
+ 查看详情 |
{% empty %}
- | 暂无处理历史,先去执行一次审核任务。 |
+ | 暂无处理历史,先去执行一次审核任务。 |
{% endfor %}
diff --git a/tests/test_audit.py b/tests/test_audit.py
index 36d690b..9ffe012 100644
--- a/tests/test_audit.py
+++ b/tests/test_audit.py
@@ -197,6 +197,95 @@ def test_audit_list_supports_batch_and_product_filters(client, db):
assert "产品B" not in content
+def test_audit_list_shows_risk_and_notification_status(client, db):
+ create_audit_log(
+ "document_review",
+ "注册审核智能体",
+ "问题一",
+ AgentResult(
+ answer="回答一",
+ status="success",
+ structured_output={"highest_risk_level": "high"},
+ ),
+ batch_id="SUB-20260604-001",
+ conversation_id="conv-001",
+ product_name="产品A",
+ )
+ create_notification_record(
+ batch_id="SUB-20260604-001",
+ conversation_id="conv-001",
+ product_name="产品A",
+ trigger_source="risk_report",
+ notify_reason="task_completed",
+ owner_role="注册资料负责人",
+ feishu_user_id="ou_demo_1",
+ message_status="sent",
+ web_detail_url="https://example.com/detail/1",
+ receipt={"message_id": "msg-1"},
+ )
+
+ response = client.get(reverse("audit:list"))
+
+ content = response.content.decode("utf-8")
+ assert response.status_code == 200
+ assert "风险状态" in content
+ assert "high" in content
+ assert "通知状态" in content
+ assert "sent" in content
+
+
+def test_audit_list_can_filter_by_notification_status(client, db):
+ create_audit_log(
+ "document_review",
+ "注册审核智能体",
+ "问题一",
+ AgentResult(answer="回答一", status="success"),
+ batch_id="SUB-20260604-001",
+ conversation_id="conv-001",
+ product_name="产品A",
+ )
+ create_audit_log(
+ "document_review",
+ "注册审核智能体",
+ "问题二",
+ AgentResult(answer="回答二", status="failed"),
+ batch_id="SUB-20260604-002",
+ conversation_id="conv-002",
+ product_name="产品B",
+ )
+ create_notification_record(
+ batch_id="SUB-20260604-001",
+ conversation_id="conv-001",
+ product_name="产品A",
+ trigger_source="risk_report",
+ notify_reason="task_completed",
+ owner_role="注册资料负责人",
+ feishu_user_id="ou_demo_1",
+ message_status="sent",
+ web_detail_url="https://example.com/detail/1",
+ receipt={"message_id": "msg-1"},
+ )
+ create_notification_record(
+ batch_id="SUB-20260604-002",
+ conversation_id="conv-002",
+ product_name="产品B",
+ trigger_source="risk_report",
+ notify_reason="task_failed",
+ owner_role="注册资料负责人",
+ feishu_user_id="ou_demo_2",
+ message_status="failed",
+ web_detail_url="https://example.com/detail/2",
+ receipt={"message_id": "msg-2"},
+ )
+
+ response = client.get(reverse("audit:list"), {"notify_status": "failed"})
+
+ content = response.content.decode("utf-8")
+ assert response.status_code == 200
+ assert "产品B" in content
+ assert "产品A" not in content
+
+
def test_audit_detail_page_shows_conversation_node_results(client, db):
Conversation.objects.create(
conversation_id="conv-001",