feat: 增强审核智能体页风险与通知能力卡展示
This commit is contained in:
@@ -86,6 +86,8 @@ def detail(request, conversation_id: str):
|
||||
conversation.refresh_from_db()
|
||||
workspace_summary = _build_workspace_summary(conversation, batch)
|
||||
export_card = _build_export_card(result, conversation)
|
||||
risk_card = _build_risk_card(result, conversation)
|
||||
notify_card = _build_notify_card(result, conversation)
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -105,6 +107,8 @@ def detail(request, conversation_id: str):
|
||||
"workspace_summary": workspace_summary,
|
||||
"upload_form": upload_form,
|
||||
"export_card": export_card,
|
||||
"risk_card": risk_card,
|
||||
"notify_card": notify_card,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -265,6 +269,55 @@ def _build_export_card(result: AgentResult | None, conversation: Conversation) -
|
||||
}
|
||||
|
||||
|
||||
def _build_risk_card(result: AgentResult | None, conversation: Conversation) -> dict:
|
||||
structured_output = {}
|
||||
if result and result.structured_output:
|
||||
structured_output = result.structured_output
|
||||
else:
|
||||
structured_output = (conversation.latest_summary or {}).get("structured_output") or {}
|
||||
if structured_output.get("output_type") != "registration_risk_report":
|
||||
return {}
|
||||
return {
|
||||
"summary": structured_output.get("summary", ""),
|
||||
"highest_risk_level": structured_output.get("highest_risk_level", ""),
|
||||
"pass_status": structured_output.get("pass_status", ""),
|
||||
"manual_review_items": structured_output.get("manual_review_items") or [],
|
||||
"risk_items": structured_output.get("risk_items") or [],
|
||||
"owner_roles": structured_output.get("owner_roles") or [],
|
||||
}
|
||||
|
||||
|
||||
def _build_notify_card(result: AgentResult | None, conversation: Conversation) -> dict:
|
||||
latest_summary = conversation.latest_summary or {}
|
||||
structured_output = latest_summary.get("structured_output") or {}
|
||||
notification_payload = latest_summary.get("notification_payload") or {}
|
||||
|
||||
if result and result.structured_output:
|
||||
structured_output = result.structured_output
|
||||
if result and result.notification_payload:
|
||||
notification_payload = result.notification_payload
|
||||
|
||||
notify_reason = (
|
||||
structured_output.get("notify_reason")
|
||||
or notification_payload.get("notify_reason")
|
||||
or ""
|
||||
)
|
||||
mentioned_users = structured_output.get("mentioned_users") or notification_payload.get("mentioned_users") or []
|
||||
message_status = structured_output.get("message_status") or notification_payload.get("message_status") or ""
|
||||
web_detail_url = structured_output.get("web_detail_url") or notification_payload.get("web_detail_url") or ""
|
||||
owners = structured_output.get("owner_roles") or notification_payload.get("owners") or []
|
||||
|
||||
if not any([notify_reason, mentioned_users, message_status, web_detail_url, owners]):
|
||||
return {}
|
||||
return {
|
||||
"notify_reason": notify_reason,
|
||||
"mentioned_users": mentioned_users,
|
||||
"message_status": message_status,
|
||||
"web_detail_url": web_detail_url,
|
||||
"owners": owners,
|
||||
}
|
||||
|
||||
|
||||
def _apply_agent_result_to_conversation(conversation: Conversation, result: AgentResult) -> None:
|
||||
conversation.task_status = result.status
|
||||
if result.node_results:
|
||||
|
||||
@@ -88,65 +88,154 @@
|
||||
|
||||
<article class="panel">
|
||||
<h2 class="section-title">节点式结果</h2>
|
||||
{% if export_card %}
|
||||
{% if export_card or risk_card or notify_card %}
|
||||
<div class="stack">
|
||||
<div class="detail-item">
|
||||
<strong>Word 导出能力卡</strong>
|
||||
<div>模板:{{ export_card.template_name }} / {{ export_card.template_version|default:"-" }}</div>
|
||||
<div>当前导出状态:{{ export_card.export_status|default:"-" }}</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">回填字段表</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>占位符</th>
|
||||
<th>字段名</th>
|
||||
<th>字段值</th>
|
||||
<th>来源</th>
|
||||
<th>回填状态</th>
|
||||
<th>是否必填</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in export_card.filled_fields %}
|
||||
<tr>
|
||||
<td>{{ item.placeholder }}</td>
|
||||
<td>{{ item.field_name }}</td>
|
||||
<td>{{ item.field_value }}</td>
|
||||
<td>{{ item.source }}</td>
|
||||
<td>{{ item.fill_status }}</td>
|
||||
<td>{{ item.required|yesno:"是,否" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6">当前暂无回填字段。</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if risk_card %}
|
||||
<div class="detail-item">
|
||||
<strong>风险预警能力卡</strong>
|
||||
<div>总风险等级:{{ risk_card.highest_risk_level|default:"-" }}</div>
|
||||
<div>是否通过:{{ risk_card.pass_status|default:"-" }}</div>
|
||||
{% if risk_card.summary %}
|
||||
<div>{{ risk_card.summary }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">待复核与风险项</h3>
|
||||
<ul class="detail-list">
|
||||
{% for item in risk_card.manual_review_items %}
|
||||
<li class="detail-item">{{ item }}</li>
|
||||
{% empty %}
|
||||
{% for item in risk_card.risk_items %}
|
||||
<li class="detail-item">
|
||||
<strong>{{ item.title|default:item.issue }}</strong>
|
||||
<div class="muted">{{ item.risk_level|default:"-" }}</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="detail-item">当前无待复核项。</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">责任角色</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>owner_role</th>
|
||||
<th>owner_name</th>
|
||||
<th>department</th>
|
||||
<th>chapter_scope</th>
|
||||
<th>risk_scope</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in risk_card.owner_roles %}
|
||||
<tr>
|
||||
<td>{{ item.owner_role }}</td>
|
||||
<td>{{ item.owner_name }}</td>
|
||||
<td>{{ item.department }}</td>
|
||||
<td>{{ item.chapter_scope }}</td>
|
||||
<td>{{ item.risk_scope }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5">当前无责任角色。</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="detail-item">
|
||||
{% if export_card %}
|
||||
<strong>Word 导出能力卡</strong>
|
||||
<div>模板:{{ export_card.template_name }} / {{ export_card.template_version|default:"-" }}</div>
|
||||
<div>当前导出状态:{{ export_card.export_status|default:"-" }}</div>
|
||||
{% elif notify_card %}
|
||||
<strong>飞书通知能力卡</strong>
|
||||
<div>通知原因:{{ notify_card.notify_reason|default:"-" }}</div>
|
||||
<div>消息状态:{{ notify_card.message_status|default:"-" }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">拦截项区</h3>
|
||||
<ul class="detail-list">
|
||||
{% for item in export_card.blocked_fields %}
|
||||
<li class="detail-item">
|
||||
<strong>{{ item.field_name }}</strong>
|
||||
<div>拦截原因:{{ item.block_reason }}</div>
|
||||
<div class="muted">来源:{{ item.risk_source }}</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="detail-item">当前无拦截项。</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% if export_card %}
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">回填字段表</h3>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>占位符</th>
|
||||
<th>字段名</th>
|
||||
<th>字段值</th>
|
||||
<th>来源</th>
|
||||
<th>回填状态</th>
|
||||
<th>是否必填</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in export_card.filled_fields %}
|
||||
<tr>
|
||||
<td>{{ item.placeholder }}</td>
|
||||
<td>{{ item.field_name }}</td>
|
||||
<td>{{ item.field_value }}</td>
|
||||
<td>{{ item.source }}</td>
|
||||
<td>{{ item.fill_status }}</td>
|
||||
<td>{{ item.required|yesno:"是,否" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6">当前暂无回填字段。</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if export_card %}
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">拦截项区</h3>
|
||||
<ul class="detail-list">
|
||||
{% for item in export_card.blocked_fields %}
|
||||
<li class="detail-item">
|
||||
<strong>{{ item.field_name }}</strong>
|
||||
<div>拦截原因:{{ item.block_reason }}</div>
|
||||
<div class="muted">来源:{{ item.risk_source }}</div>
|
||||
</li>
|
||||
{% empty %}
|
||||
<li class="detail-item">当前无拦截项。</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if notify_card %}
|
||||
<div class="panel" style="padding: 14px;">
|
||||
<h3 class="section-title" style="font-size: 1rem;">飞书通知能力卡</h3>
|
||||
<ul class="detail-list">
|
||||
<li class="detail-item"><strong>通知原因</strong><div>{{ notify_card.notify_reason|default:"-" }}</div></li>
|
||||
<li class="detail-item"><strong>消息状态</strong><div>{{ notify_card.message_status|default:"-" }}</div></li>
|
||||
<li class="detail-item"><strong>被 @ 处理人</strong><div>{{ notify_card.mentioned_users|join:" / "|default:"-" }}</div></li>
|
||||
<li class="detail-item"><strong>Web 详情链接</strong><div>{{ notify_card.web_detail_url|default:"-" }}</div></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="button-row">
|
||||
<a class="button" href="{% url 'platform_ui:knowledge-base' %}?view=template_mappings">维护 Word 模板</a>
|
||||
<a class="button" href="{% url 'platform_ui:knowledge-base' %}?view=field_schemas">维护字段映射</a>
|
||||
<a class="button" href="{% url 'audit:list' %}">查看导出记录</a>
|
||||
{% if export_card %}
|
||||
<a class="button" href="{% url 'platform_ui:knowledge-base' %}?view=template_mappings">维护 Word 模板</a>
|
||||
<a class="button" href="{% url 'platform_ui:knowledge-base' %}?view=field_schemas">维护字段映射</a>
|
||||
<a class="button" href="{% url 'audit:list' %}">查看导出记录</a>
|
||||
{% endif %}
|
||||
{% if risk_card %}
|
||||
<a class="button" href="{% url 'platform_ui:knowledge-base' %}?view=owner_mappings">查看责任人映射</a>
|
||||
{% endif %}
|
||||
{% if notify_card %}
|
||||
<a class="button" href="{% url 'platform_ui:knowledge-base' %}?view=feishu_configs">查看飞书配置</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% elif result and result.structured_output %}
|
||||
|
||||
@@ -487,6 +487,62 @@ def test_chat_page_shows_word_export_field_table_and_governance_entries(client,
|
||||
assert "维护字段映射" in content
|
||||
|
||||
|
||||
def test_chat_page_shows_risk_and_notification_cards_from_conversation_summary(client, db):
|
||||
batch, conversation = _create_conversation_with_batch()
|
||||
conversation.node_results = [
|
||||
{"label": "资料包导入", "status": "已完成"},
|
||||
{"label": "目录汇总", "status": "已完成"},
|
||||
{"label": "法规完整性检查", "status": "已完成"},
|
||||
{"label": "字段抽取", "status": "已完成"},
|
||||
{"label": "一致性核查", "status": "待复核"},
|
||||
{"label": "风险预警", "status": "已阻断"},
|
||||
{"label": "Word 回填导出", "status": "待复核"},
|
||||
{"label": "飞书通知", "status": "已完成"},
|
||||
]
|
||||
conversation.latest_summary = {
|
||||
"structured_output": {
|
||||
"output_type": "registration_risk_report",
|
||||
"summary": "存在高风险项,需人工复核。",
|
||||
"highest_risk_level": "high",
|
||||
"pass_status": "blocked",
|
||||
"manual_review_items": ["CH1.11.5 沟通记录缺失"],
|
||||
"risk_items": [{"title": "产品名称跨文档不一致", "risk_level": "high"}],
|
||||
"owner_roles": [
|
||||
{
|
||||
"owner_role": "注册资料负责人",
|
||||
"owner_name": "张三",
|
||||
"department": "注册事务部",
|
||||
"chapter_scope": "CH1",
|
||||
"risk_scope": "字段冲突",
|
||||
"feishu_user_id": "ou_demo_1",
|
||||
"feishu_open_id": "on_demo_1",
|
||||
"feishu_name": "张三",
|
||||
"notify_enabled": True,
|
||||
}
|
||||
],
|
||||
"notify_reason": "task_completed",
|
||||
"mentioned_users": ["ou_demo_1"],
|
||||
"message_status": "sent",
|
||||
"web_detail_url": "https://example.com/audit/1",
|
||||
}
|
||||
}
|
||||
conversation.save(update_fields=["node_results", "latest_summary", "updated_at"])
|
||||
|
||||
response = client.get(reverse("chat:detail", args=[conversation.conversation_id]))
|
||||
|
||||
content = response.content.decode("utf-8")
|
||||
assert response.status_code == 200
|
||||
assert "风险预警能力卡" in content
|
||||
assert "总风险等级" in content
|
||||
assert "high" in content
|
||||
assert "注册资料负责人" in content
|
||||
assert "CH1.11.5 沟通记录缺失" in content
|
||||
assert "飞书通知能力卡" in content
|
||||
assert "task_completed" in content
|
||||
assert "sent" in content
|
||||
assert "ou_demo_1" in content
|
||||
|
||||
|
||||
def test_chat_upload_keeps_existing_conversation_binding_and_adds_documents(client, db):
|
||||
batch, conversation = _create_conversation_with_batch()
|
||||
existing_document = UploadedDocument.objects.create(
|
||||
|
||||
Reference in New Issue
Block a user