feat: 增强处理历史页指标与上下文跳转
This commit is contained in:
@@ -144,6 +144,28 @@ def build_history_rows(logs) -> list[dict]:
|
|||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def build_history_metrics(history_rows: list[dict]) -> list[dict]:
|
||||||
|
"""
|
||||||
|
为处理历史页生成顶部指标卡。
|
||||||
|
|
||||||
|
口径保持前台可讲解:
|
||||||
|
- 处理任务数:当前筛选结果中的执行记录数
|
||||||
|
- 成功执行:状态为 success 的记录数
|
||||||
|
- 通知已发送:通知状态为 sent 的记录数
|
||||||
|
- 高风险阻断:风险等级为 high 的记录数
|
||||||
|
"""
|
||||||
|
total_count = len(history_rows)
|
||||||
|
success_count = sum(1 for row in history_rows if row["log"].status == "success")
|
||||||
|
notify_sent_count = sum(1 for row in history_rows if row.get("notify_status") == "sent")
|
||||||
|
blocked_count = sum(1 for row in history_rows if row.get("risk_status") == "high")
|
||||||
|
return [
|
||||||
|
{"label": "处理任务数", "value": total_count, "note": "按当前筛选条件回看执行留痕。"},
|
||||||
|
{"label": "成功执行", "value": success_count, "note": "执行完成并写入审计快照。"},
|
||||||
|
{"label": "通知已发送", "value": notify_sent_count, "note": "已生成 sent 状态的通知留痕。"},
|
||||||
|
{"label": "高风险阻断", "value": blocked_count, "note": "风险等级为 high 的处理记录。"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def build_detail_summary(log: AgentAuditLog, conversation, notifications) -> dict:
|
def build_detail_summary(log: AgentAuditLog, conversation, notifications) -> dict:
|
||||||
"""
|
"""
|
||||||
组装处理历史详情页的导出摘要与通知回执信息。
|
组装处理历史详情页的导出摘要与通知回执信息。
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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_detail_summary, build_history_rows
|
from .services import build_detail_summary, build_history_metrics, build_history_rows
|
||||||
|
|
||||||
|
|
||||||
def log_list(request):
|
def log_list(request):
|
||||||
@@ -35,11 +35,13 @@ def log_list(request):
|
|||||||
if (log.structured_output or {}).get("highest_risk_level") == risk_status
|
if (log.structured_output or {}).get("highest_risk_level") == risk_status
|
||||||
or (log.structured_output or {}).get("risk_level") == risk_status
|
or (log.structured_output or {}).get("risk_level") == risk_status
|
||||||
]
|
]
|
||||||
|
history_rows = build_history_rows(logs)
|
||||||
return render(
|
return render(
|
||||||
request,
|
request,
|
||||||
"audit/log_list.html",
|
"audit/log_list.html",
|
||||||
{
|
{
|
||||||
"history_rows": build_history_rows(logs),
|
"history_rows": history_rows,
|
||||||
|
"history_metrics": build_history_metrics(history_rows),
|
||||||
"selected_scenario_id": scenario_id,
|
"selected_scenario_id": scenario_id,
|
||||||
"keyword": keyword,
|
"keyword": keyword,
|
||||||
"notify_status": notify_status,
|
"notify_status": notify_status,
|
||||||
|
|||||||
@@ -9,6 +9,16 @@
|
|||||||
<p class="page-lead">按批次、产品和会话回看审核执行、结构化结论与通知留痕。</p>
|
<p class="page-lead">按批次、产品和会话回看审核执行、结构化结论与通知留痕。</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="metric-grid">
|
||||||
|
{% for item in history_metrics %}
|
||||||
|
<article class="metric-card">
|
||||||
|
<div class="metric-label">{{ item.label }}</div>
|
||||||
|
<div class="metric-value">{{ item.value }}</div>
|
||||||
|
<div class="metric-note">{{ item.note }}</div>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="panel">
|
<section class="panel">
|
||||||
<div class="section-heading">
|
<div class="section-heading">
|
||||||
<div>
|
<div>
|
||||||
@@ -79,19 +89,55 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{ row.log.id }}</td>
|
<td>{{ row.log.id }}</td>
|
||||||
<td>{{ row.log.scenario_name }}</td>
|
<td>{{ row.log.scenario_name }}</td>
|
||||||
<td>{{ row.log.product_name|default:"-" }}</td>
|
<td class="cell-min-220">
|
||||||
<td>{{ row.log.batch_id|default:"-" }}</td>
|
{% if row.log.product_name %}
|
||||||
<td>{{ row.log.conversation_id|default:"-" }}</td>
|
<a href="{% url 'audit:list' %}?keyword={{ row.log.product_name }}">{{ row.log.product_name }}</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="nowrap">
|
||||||
|
{% if row.log.batch_id %}
|
||||||
|
<a href="{% url 'documents:list' %}?keyword={{ row.log.batch_id }}">{{ row.log.batch_id }}</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="cell-min-220">
|
||||||
|
{% if row.log.conversation_id %}
|
||||||
|
<a class="button" href="{% url 'chat:detail' row.log.conversation_id %}">查看会话 {{ row.log.conversation_id }}</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ row.batch_scale }}</td>
|
<td>{{ row.batch_scale }}</td>
|
||||||
<td>{{ row.batch_status }}</td>
|
<td>
|
||||||
<td>{{ row.conversation_status }}</td>
|
<span class="pill {% if row.batch_status == '已完成' %}pill-success{% elif row.batch_status == '待复核' %}pill-signal{% else %}pill-danger{% endif %}">
|
||||||
|
{{ row.batch_status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="pill {% if row.conversation_status == 'success' %}pill-success{% elif row.conversation_status == 'failed' %}pill-danger{% else %}pill-signal{% endif %}">
|
||||||
|
{{ row.conversation_status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>{{ row.log.get_user_input_summary }}</td>
|
<td>{{ row.log.get_user_input_summary }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="pill {% if row.log.status == 'success' %}pill-success{% else %}pill-danger{% endif %}">{{ row.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>{{ row.risk_status }}</td>
|
<td>
|
||||||
<td>{{ row.notify_reason }}</td>
|
<span class="pill {% if row.risk_status == 'high' %}pill-danger{% elif row.risk_status == 'medium' %}pill-signal{% elif row.risk_status == 'low' %}pill-success{% endif %}">
|
||||||
<td>{{ row.notify_status }}</td>
|
{{ row.risk_status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="pill pill-accent">{{ row.notify_reason }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="pill {% if row.notify_status == 'sent' %}pill-success{% elif row.notify_status == 'failed' %}pill-danger{% else %}pill-signal{% endif %}">
|
||||||
|
{{ row.notify_status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>{{ row.log.model_name }}</td>
|
<td>{{ row.log.model_name }}</td>
|
||||||
<td>{{ row.log.created_at|date:"Y-m-d H:i" }}</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>
|
<td><a class="button" href="{% url 'audit:detail' row.log.id %}">查看详情</a></td>
|
||||||
|
|||||||
@@ -399,6 +399,66 @@ def test_audit_list_shows_batch_scale_and_conversation_status(client, db):
|
|||||||
assert "待复核" in content
|
assert "待复核" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_audit_list_shows_history_metrics_and_context_links(client, db):
|
||||||
|
SubmissionBatch.objects.create(
|
||||||
|
batch_id="SUB-20260604-009",
|
||||||
|
product_name="产品C",
|
||||||
|
workflow_type="registration",
|
||||||
|
conversation_id="conv-009",
|
||||||
|
file_count=5,
|
||||||
|
page_count=31,
|
||||||
|
import_status="completed",
|
||||||
|
)
|
||||||
|
Conversation.objects.create(
|
||||||
|
conversation_id="conv-009",
|
||||||
|
title="产品C",
|
||||||
|
product_name="产品C",
|
||||||
|
batch_id="SUB-20260604-009",
|
||||||
|
task_status="success",
|
||||||
|
node_results=[
|
||||||
|
{"label": "风险预警", "status": "已完成"},
|
||||||
|
{"label": "飞书通知", "status": "已完成"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
create_audit_log(
|
||||||
|
"document_review",
|
||||||
|
"注册审核智能体",
|
||||||
|
"产品C 审核",
|
||||||
|
AgentResult(
|
||||||
|
answer="完成",
|
||||||
|
status="success",
|
||||||
|
structured_output={"highest_risk_level": "medium"},
|
||||||
|
),
|
||||||
|
batch_id="SUB-20260604-009",
|
||||||
|
conversation_id="conv-009",
|
||||||
|
product_name="产品C",
|
||||||
|
)
|
||||||
|
create_notification_record(
|
||||||
|
batch_id="SUB-20260604-009",
|
||||||
|
conversation_id="conv-009",
|
||||||
|
product_name="产品C",
|
||||||
|
trigger_source="risk_report",
|
||||||
|
notify_reason="task_completed",
|
||||||
|
owner_role="注册资料负责人",
|
||||||
|
feishu_user_id="ou_demo_9",
|
||||||
|
message_status="sent",
|
||||||
|
web_detail_url="https://example.com/detail/9",
|
||||||
|
receipt={"message_id": "msg-9"},
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get(reverse("audit:list"))
|
||||||
|
|
||||||
|
content = response.content.decode("utf-8")
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "处理任务数" in content
|
||||||
|
assert "成功执行" in content
|
||||||
|
assert "通知已发送" in content
|
||||||
|
assert "高风险阻断" in content
|
||||||
|
assert reverse("chat:detail", args=["conv-009"]) in content
|
||||||
|
assert f"{reverse('documents:list')}?keyword=SUB-20260604-009" in content
|
||||||
|
assert f"{reverse('audit:list')}?keyword=产品C" in content
|
||||||
|
|
||||||
|
|
||||||
def test_audit_detail_page_shows_export_summary_and_notification_receipt(client, db):
|
def test_audit_detail_page_shows_export_summary_and_notification_receipt(client, db):
|
||||||
Conversation.objects.create(
|
Conversation.objects.create(
|
||||||
conversation_id="conv-002",
|
conversation_id="conv-002",
|
||||||
|
|||||||
Reference in New Issue
Block a user