diff --git a/apps/audit/services.py b/apps/audit/services.py index 127be16..bd17145 100644 --- a/apps/audit/services.py +++ b/apps/audit/services.py @@ -133,6 +133,53 @@ def create_notification_record( ) +def build_history_list_context( + *, + scenario_id: str = "", + keyword: str = "", + notify_status: str = "", + risk_status: str = "", +) -> dict: + """ + 组装处理历史列表页所需的筛选结果与展示上下文。 + + View 只负责读取 query params,筛选逻辑和列表聚合统一在服务层完成。 + """ + 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 + ] + if risk_status: + logs = [ + log + for log in logs + if (log.structured_output or {}).get("highest_risk_level") == risk_status + or (log.structured_output or {}).get("risk_level") == risk_status + ] + history_rows = build_history_rows(logs) + return { + "history_rows": history_rows, + "history_metrics": build_history_metrics(history_rows), + "selected_scenario_id": scenario_id, + "keyword": keyword, + "notify_status": notify_status, + "risk_status": risk_status, + } + + def build_history_rows(logs) -> list[dict]: """ 为处理历史列表补齐风险状态和通知状态。 diff --git a/apps/audit/views.py b/apps/audit/views.py index faa3c66..0448bf1 100644 --- a/apps/audit/views.py +++ b/apps/audit/views.py @@ -4,55 +4,20 @@ from .models import AgentAuditLog, NotificationRecord from apps.chat.models import Conversation from .services import ( build_detail_summary, - build_history_metrics, - build_history_rows, + build_history_list_context, normalize_conversation_node_results, ) 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() - risk_status = (request.GET.get("risk_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 - ] - if risk_status: - logs = [ - log - for log in logs - if (log.structured_output or {}).get("highest_risk_level") == risk_status - or (log.structured_output or {}).get("risk_level") == risk_status - ] - history_rows = build_history_rows(logs) - return render( - request, - "audit/log_list.html", - { - "history_rows": history_rows, - "history_metrics": build_history_metrics(history_rows), - "selected_scenario_id": scenario_id, - "keyword": keyword, - "notify_status": notify_status, - "risk_status": risk_status, - }, + context = build_history_list_context( + 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(), + risk_status=(request.GET.get("risk_status") or "").strip(), ) + return render(request, "audit/log_list.html", context) def log_detail(request, log_id: int): diff --git a/tests/test_audit.py b/tests/test_audit.py index 904ff4b..b1edc69 100644 --- a/tests/test_audit.py +++ b/tests/test_audit.py @@ -2,7 +2,7 @@ from django.urls import reverse from agent_core.results import AgentResult from apps.audit.models import AgentAuditLog, DemoBusinessRecord, NotificationRecord -from apps.audit.services import create_audit_log, create_notification_record +from apps.audit.services import build_history_list_context, create_audit_log, create_notification_record from apps.chat.models import Conversation from apps.documents.models import SubmissionBatch from agent_core.tools.builtin_tools import query_demo_records @@ -344,6 +344,71 @@ def test_audit_list_can_filter_by_risk_status(client, db): assert "产品B" not in content +def test_build_history_list_context_filters_by_keyword_notify_and_risk(db): + create_audit_log( + "document_review", + "注册审核智能体", + "问题一", + AgentResult( + answer="回答一", + status="success", + structured_output={"highest_risk_level": "high"}, + ), + batch_id="SUB-20260604-011", + conversation_id="conv-011", + product_name="产品A", + ) + create_audit_log( + "document_review", + "注册审核智能体", + "问题二", + AgentResult( + answer="回答二", + status="success", + structured_output={"highest_risk_level": "low"}, + ), + batch_id="SUB-20260604-012", + conversation_id="conv-012", + product_name="产品B", + ) + create_notification_record( + batch_id="SUB-20260604-011", + conversation_id="conv-011", + product_name="产品A", + trigger_source="risk_report", + notify_reason="task_completed", + owner_role="注册资料负责人", + feishu_user_id="ou_demo_11", + message_status="sent", + web_detail_url="https://example.com/detail/11", + receipt={"message_id": "msg-11"}, + ) + create_notification_record( + batch_id="SUB-20260604-012", + conversation_id="conv-012", + product_name="产品B", + trigger_source="risk_report", + notify_reason="task_failed", + owner_role="注册资料负责人", + feishu_user_id="ou_demo_12", + message_status="failed", + web_detail_url="https://example.com/detail/12", + receipt={"message_id": "msg-12"}, + ) + + context = build_history_list_context( + keyword="产品A", + notify_status="sent", + risk_status="high", + ) + + assert context["keyword"] == "产品A" + assert context["notify_status"] == "sent" + assert context["risk_status"] == "high" + assert len(context["history_rows"]) == 1 + assert context["history_rows"][0]["log"].product_name == "产品A" + + def test_audit_detail_page_shows_conversation_node_results(client, db): Conversation.objects.create( conversation_id="conv-001",