feat: 增强处理历史风险与通知状态展示
This commit is contained in:
@@ -94,3 +94,29 @@ def create_notification_record(
|
|||||||
web_detail_url=web_detail_url,
|
web_detail_url=web_detail_url,
|
||||||
receipt=receipt,
|
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
|
||||||
|
|||||||
@@ -2,24 +2,39 @@ from django.shortcuts import get_object_or_404, render
|
|||||||
|
|
||||||
from .models import AgentAuditLog, NotificationRecord
|
from .models import AgentAuditLog, NotificationRecord
|
||||||
from apps.chat.models import Conversation
|
from apps.chat.models import Conversation
|
||||||
|
from .services import build_history_rows
|
||||||
|
|
||||||
|
|
||||||
def log_list(request):
|
def log_list(request):
|
||||||
# 处理历史页支持按批次、产品和状态筛选。
|
# 处理历史页支持按批次、产品和状态筛选。
|
||||||
scenario_id = (request.GET.get("scenario_id") or "").strip()
|
scenario_id = (request.GET.get("scenario_id") or "").strip()
|
||||||
keyword = (request.GET.get("keyword") or "").strip()
|
keyword = (request.GET.get("keyword") or "").strip()
|
||||||
|
notify_status = (request.GET.get("notify_status") or "").strip()
|
||||||
logs = AgentAuditLog.objects.all()
|
logs = AgentAuditLog.objects.all()
|
||||||
if scenario_id:
|
if scenario_id:
|
||||||
logs = logs.filter(scenario_id=scenario_id)
|
logs = logs.filter(scenario_id=scenario_id)
|
||||||
if keyword:
|
if keyword:
|
||||||
logs = logs.filter(product_name__icontains=keyword) | logs.filter(batch_id__icontains=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(
|
return render(
|
||||||
request,
|
request,
|
||||||
"audit/log_list.html",
|
"audit/log_list.html",
|
||||||
{
|
{
|
||||||
"logs": logs,
|
"history_rows": build_history_rows(logs),
|
||||||
"selected_scenario_id": scenario_id,
|
"selected_scenario_id": scenario_id,
|
||||||
"keyword": keyword,
|
"keyword": keyword,
|
||||||
|
"notify_status": notify_status,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,15 @@
|
|||||||
<label for="id_keyword">产品名称 / 批次号</label>
|
<label for="id_keyword">产品名称 / 批次号</label>
|
||||||
<input id="id_keyword" type="text" name="keyword" value="{{ keyword }}" placeholder="例如:新型冠状病毒 或 SUB-20260604-001">
|
<input id="id_keyword" type="text" name="keyword" value="{{ keyword }}" placeholder="例如:新型冠状病毒 或 SUB-20260604-001">
|
||||||
</div>
|
</div>
|
||||||
<div class="button-row" style="align-items: end;">
|
<div>
|
||||||
|
<label for="id_notify_status">通知状态</label>
|
||||||
|
<select id="id_notify_status" name="notify_status">
|
||||||
|
<option value="">全部状态</option>
|
||||||
|
<option value="sent"{% if notify_status == "sent" %} selected{% endif %}>sent</option>
|
||||||
|
<option value="failed"{% if notify_status == "failed" %} selected{% endif %}>failed</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
<button type="submit">筛选历史</button>
|
<button type="submit">筛选历史</button>
|
||||||
<a class="button" href="{% url 'audit:list' %}">清空</a>
|
<a class="button" href="{% url 'audit:list' %}">清空</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -46,29 +54,33 @@
|
|||||||
<th>会话</th>
|
<th>会话</th>
|
||||||
<th>输入摘要</th>
|
<th>输入摘要</th>
|
||||||
<th>状态</th>
|
<th>状态</th>
|
||||||
|
<th>风险状态</th>
|
||||||
|
<th>通知状态</th>
|
||||||
<th>模型</th>
|
<th>模型</th>
|
||||||
<th>时间</th>
|
<th>时间</th>
|
||||||
<th>详情</th>
|
<th>详情</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for log in logs %}
|
{% for row in history_rows %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ log.id }}</td>
|
<td>{{ row.log.id }}</td>
|
||||||
<td>{{ log.scenario_name }}</td>
|
<td>{{ row.log.scenario_name }}</td>
|
||||||
<td>{{ log.product_name|default:"-" }}</td>
|
<td>{{ row.log.product_name|default:"-" }}</td>
|
||||||
<td>{{ log.batch_id|default:"-" }}</td>
|
<td>{{ row.log.batch_id|default:"-" }}</td>
|
||||||
<td>{{ log.conversation_id|default:"-" }}</td>
|
<td>{{ row.log.conversation_id|default:"-" }}</td>
|
||||||
<td>{{ log.get_user_input_summary }}</td>
|
<td>{{ row.log.get_user_input_summary }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pill {% if log.status == 'success' %}pill-success{% else %}pill-danger{% endif %}">{{ log.get_status_display_text }}</span>
|
<span class="pill {% if row.log.status == 'success' %}pill-success{% else %}pill-danger{% endif %}">{{ row.log.get_status_display_text }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ log.model_name }}</td>
|
<td>{{ row.risk_status }}</td>
|
||||||
<td>{{ log.created_at|date:"Y-m-d H:i" }}</td>
|
<td>{{ row.notify_status }}</td>
|
||||||
<td><a class="button" href="{% url 'audit:detail' log.id %}">查看详情</a></td>
|
<td>{{ row.log.model_name }}</td>
|
||||||
|
<td>{{ row.log.created_at|date:"Y-m-d H:i" }}</td>
|
||||||
|
<td><a class="button" href="{% url 'audit:detail' row.log.id %}">查看详情</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr><td colspan="10">暂无处理历史,先去执行一次审核任务。</td></tr>
|
<tr><td colspan="12">暂无处理历史,先去执行一次审核任务。</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -197,6 +197,95 @@ def test_audit_list_supports_batch_and_product_filters(client, db):
|
|||||||
assert "产品B" not in content
|
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):
|
def test_audit_detail_page_shows_conversation_node_results(client, db):
|
||||||
Conversation.objects.create(
|
Conversation.objects.create(
|
||||||
conversation_id="conv-001",
|
conversation_id="conv-001",
|
||||||
|
|||||||
Reference in New Issue
Block a user