bea288e414
== 已部署上线 (9项) == - 代办事项真实数据源集成 (企微审批API 8bug修复链) - H5/坐席端 Logo样式统一+绿色背景 - 视频引导页修复 (localStorage key v2) - 坐席端 v9 Vue版本修复 (ElMessage._context) - 截图按钮 v10 修复 (getDisplayMedia user gesture) - 扫码样式恢复+H5扫码登录跳转修复 - H5截图快捷键提示 == 代码完成待部署 (3项) == - 知识迭代3Bug修复 (#8 POST端点/#7 MERGE幂等/#6 过期检查) - 会议室预定-小鱼易联终端 (40文件, 40/40测试通过) - IT资产升级审批推送 (asset_service.py) == 需求文档 (2项) == - 坐席端AI辅助消息框-PRD (4项新功能确认) - 坐席端布局优化建议 v2.0 (7天计划) == 新增文档 == - 日报-2026-07-11.md - 知识迭代Bug修复报告-20260711.md - 会议室预定-部署指南.md - CHANGELOG.md 更新 == 测试 == - test_todo_integration.py: 40/40 - test_meetingroom.py: 40/40 - test_bugfix_ki_suggestions.py: 21/21
639 lines
23 KiB
Python
639 lines
23 KiB
Python
# =============================================================================
|
||
# 企微IT智能服务台 — 知识库自动迭代 API(Tier1 扩展)
|
||
# =============================================================================
|
||
# 说明:知识库自动迭代相关接口(扩展版)。
|
||
# 1. POST /api/admin/knowledge-iteration/analyze - 触发分析并生成建议
|
||
# 2. GET /api/admin/knowledge-iteration/suggestions - 获取建议列表(支持 audience/confidence 筛选)
|
||
# 2.1 POST /api/admin/knowledge-iteration/suggestions - 创建知识建议(手动录入,通道 B)
|
||
# 3. GET /api/admin/knowledge-iteration/suggestions/{id} - 获取建议详情
|
||
# 4. POST /api/admin/knowledge-iteration/suggestions/{id}/approve - 审核通过(触发Neo4j写图)
|
||
# 5. POST /api/admin/knowledge-iteration/suggestions/{id}/reject - 审核拒绝
|
||
# 6. POST /api/admin/knowledge-iteration/suggestions/{id}/rewrite - 改写提案(Tier1新增)
|
||
# 7. POST /api/admin/knowledge-iteration/suggestions/{id}/queue - 放入独立队列(Tier1新增)
|
||
# 8. POST /api/admin/knowledge-iteration/suggestions/{id}/dequeue-approve - 队列中审批(Tier1新增)
|
||
# 9. GET /api/admin/knowledge-iteration/stats - 获取统计
|
||
# =============================================================================
|
||
|
||
import logging
|
||
from typing import Optional
|
||
|
||
from fastapi import APIRouter, Depends, Query
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.database import get_db
|
||
from app.dependencies import get_current_user, require_admin, UserInfo
|
||
from app.models.knowledge_suggestion import KnowledgeSuggestion
|
||
from app.schemas.enums import SuggestionStatusEnum, GraphSyncStatusEnum
|
||
from app.schemas.knowledge_suggestion import (
|
||
KnowledgeSuggestionListResponse,
|
||
KnowledgeSuggestionResponse,
|
||
KnowledgeSuggestionStatsResponse,
|
||
KnowledgeSuggestionApprove,
|
||
KnowledgeSuggestionReject,
|
||
KnowledgeSuggestionRewrite,
|
||
KnowledgeSuggestionMerge,
|
||
KnowledgeSuggestionCreate,
|
||
)
|
||
from app.services.knowledge_iteration_service import (
|
||
KnowledgeIterationService,
|
||
dep_knowledge_iteration_service,
|
||
)
|
||
from app.services.neo4j_client import get_neo4j_client
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 触发分析
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/analyze
|
||
@router.post("/analyze")
|
||
@require_admin
|
||
async def trigger_analysis(
|
||
days: int = Query(default=7, ge=1, le=90, description="分析过去N天的数据"),
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""触发知识库迭代分析。
|
||
|
||
分析过去N天的标注数据和会话数据,调用 Dify AI 自动生成优化建议。
|
||
|
||
- **days**: 分析过去N天的数据(默认7天,最大90天)
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(f"管理员 {current_user.name} 触发了知识库迭代分析, days={days}")
|
||
|
||
result = await service.analyze_and_generate_suggestions(db, days=days)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "分析完成",
|
||
"data": result,
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 获取建议列表(Tier1 扩展:audience/confidence 筛选)
|
||
# -----------------------------------------------------------------------------
|
||
# GET /api/admin/knowledge-iteration/suggestions
|
||
@router.get("/suggestions")
|
||
@require_admin
|
||
async def list_suggestions(
|
||
status: Optional[str] = Query(default=None, description="筛选状态:pending/queued/approved/rejected/applied/graph_synced/expired"),
|
||
suggestion_type: Optional[str] = Query(default=None, description="筛选类型:new_faq/update/outdated"),
|
||
audience: Optional[str] = Query(default=None, description="筛选受众:employee_quick_reply/engineer_workguide"),
|
||
confidence_min: Optional[float] = Query(default=None, ge=0.0, le=1.0, description="置信度下限"),
|
||
confidence_max: Optional[float] = Query(default=None, ge=0.0, le=1.0, description="置信度上限"),
|
||
page: int = Query(default=1, ge=1, description="页码"),
|
||
page_size: int = Query(default=20, ge=1, le=100, description="每页数量"),
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""获取知识库优化建议列表(Tier1 扩展:支持 audience/confidence 筛选)。
|
||
|
||
- **status**: 筛选状态
|
||
- **suggestion_type**: 筛选类型
|
||
- **audience**: 按受众类型筛选(Tier1 新增)
|
||
- **confidence_min**: 置信度下限(Tier1 新增)
|
||
- **confidence_max**: 置信度上限(Tier1 新增)
|
||
- **page**: 页码
|
||
- **page_size**: 每页数量
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
from sqlalchemy import select, func
|
||
|
||
# 构建查询
|
||
stmt = select(KnowledgeSuggestion).order_by(
|
||
KnowledgeSuggestion.created_at.desc()
|
||
)
|
||
|
||
if status:
|
||
stmt = stmt.where(KnowledgeSuggestion.status == status)
|
||
if suggestion_type:
|
||
stmt = stmt.where(KnowledgeSuggestion.suggestion_type == suggestion_type)
|
||
if audience:
|
||
stmt = stmt.where(KnowledgeSuggestion.audience == audience)
|
||
if confidence_min is not None:
|
||
stmt = stmt.where(KnowledgeSuggestion.confidence >= confidence_min)
|
||
if confidence_max is not None:
|
||
stmt = stmt.where(KnowledgeSuggestion.confidence <= confidence_max)
|
||
|
||
# 分页
|
||
offset = (page - 1) * page_size
|
||
stmt = stmt.offset(offset).limit(page_size)
|
||
|
||
result = await db.execute(stmt)
|
||
suggestions = result.scalars().all()
|
||
|
||
# 统计总数
|
||
count_stmt = select(func.count()).select_from(KnowledgeSuggestion)
|
||
if status:
|
||
count_stmt = count_stmt.where(KnowledgeSuggestion.status == status)
|
||
if suggestion_type:
|
||
count_stmt = count_stmt.where(
|
||
KnowledgeSuggestion.suggestion_type == suggestion_type
|
||
)
|
||
if audience:
|
||
count_stmt = count_stmt.where(KnowledgeSuggestion.audience == audience)
|
||
if confidence_min is not None:
|
||
count_stmt = count_stmt.where(KnowledgeSuggestion.confidence >= confidence_min)
|
||
if confidence_max is not None:
|
||
count_stmt = count_stmt.where(KnowledgeSuggestion.confidence <= confidence_max)
|
||
|
||
total_result = await db.execute(count_stmt)
|
||
total = total_result.scalar()
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"total": total,
|
||
"items": [
|
||
KnowledgeSuggestionResponse.model_validate(s) for s in suggestions
|
||
],
|
||
},
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 创建知识建议(手动录入 — 通道 B)
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions
|
||
@router.post("/suggestions")
|
||
@require_admin
|
||
async def create_suggestion(
|
||
body: KnowledgeSuggestionCreate,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""创建知识库优化建议(手动录入,通道 B)。
|
||
|
||
训练师/管理员手动创建知识建议,状态初始为 pending,等待审核。
|
||
|
||
- **suggestion_type**: 建议类型(new_faq/update/outdated)
|
||
- **title**: 建议标题(必填)
|
||
- **content**: 答案内容(必填)
|
||
- **source_type**: 来源类型(手动录入为 manual)
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
# 字段验证:必填字段不能为空
|
||
if not body.title or not body.title.strip():
|
||
return {"code": 400, "message": "标题不能为空", "data": None}
|
||
if not body.content or not body.content.strip():
|
||
return {"code": 400, "message": "内容不能为空", "data": None}
|
||
if not body.source_type or not body.source_type.strip():
|
||
return {"code": 400, "message": "来源类型不能为空", "data": None}
|
||
|
||
logger.info(
|
||
f"管理员 {current_user.name} 手动创建知识建议: "
|
||
f"type={body.suggestion_type}, title={body.title}"
|
||
)
|
||
|
||
# 构建 KnowledgeSuggestion 对象,status 初始为 pending
|
||
suggestion = KnowledgeSuggestion(
|
||
suggestion_type=body.suggestion_type,
|
||
status=SuggestionStatusEnum.pending.value,
|
||
title=body.title.strip(),
|
||
content=body.content.strip(),
|
||
category=body.category or "其他",
|
||
tags=body.tags or [],
|
||
source_type=body.source_type,
|
||
source_data=body.source_data,
|
||
reason=body.reason,
|
||
confidence=body.confidence,
|
||
audience=body.audience.value if body.audience else None,
|
||
issue=body.issue,
|
||
action=body.action,
|
||
relation_type=body.relation_type.value if body.relation_type else None,
|
||
parent_issue=body.parent_issue,
|
||
graph_meta=body.graph_meta,
|
||
graph_sync_status=GraphSyncStatusEnum.pending.value,
|
||
source_failed=False,
|
||
)
|
||
|
||
db.add(suggestion)
|
||
await db.commit()
|
||
await db.refresh(suggestion)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "知识建议创建成功,等待审核",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 获取建议详情
|
||
# -----------------------------------------------------------------------------
|
||
# GET /api/admin/knowledge-iteration/suggestions/{id}
|
||
@router.get("/suggestions/{suggestion_id}")
|
||
@require_admin
|
||
async def get_suggestion(
|
||
suggestion_id: str,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""获取知识库优化建议详情。
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
from sqlalchemy import select
|
||
|
||
stmt = select(KnowledgeSuggestion).where(
|
||
KnowledgeSuggestion.id == suggestion_id
|
||
)
|
||
result = await db.execute(stmt)
|
||
suggestion = result.scalar_one_or_none()
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 审核通过(Tier1 扩展:串联 Neo4j 写图)
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions/{id}/approve
|
||
@router.post("/suggestions/{suggestion_id}/approve")
|
||
@require_admin
|
||
async def approve_suggestion(
|
||
suggestion_id: str,
|
||
body: KnowledgeSuggestionApprove,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""审核通过知识库优化建议(Tier1:串联 Neo4j 写图 + 五态流转)。
|
||
|
||
审核通过后:
|
||
1. 状态 pending/queued → approved → applied → graph_synced
|
||
2. 自动创建 KnowledgeBase 条目(派生视图)
|
||
3. 触发 Neo4j 图写入(D1 解读2 合一)
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(
|
||
f"管理员 {current_user.name} 审核通过建议: {suggestion_id}"
|
||
)
|
||
|
||
# 尝试获取 Neo4j 客户端(可选,不影响审批主流程)
|
||
neo4j_client = await get_neo4j_client()
|
||
|
||
suggestion = await service.approve_suggestion(
|
||
db, suggestion_id, current_user.employee_id,
|
||
neo4j_client=neo4j_client,
|
||
)
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在或状态转换无效", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "审核通过,建议已应用到知识库并同步至知识图谱",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 审核拒绝
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions/{id}/reject
|
||
@router.post("/suggestions/{suggestion_id}/reject")
|
||
@require_admin
|
||
async def reject_suggestion(
|
||
suggestion_id: str,
|
||
body: KnowledgeSuggestionReject,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""拒绝知识库优化建议。
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(
|
||
f"管理员 {current_user.name} 拒绝建议: {suggestion_id}, "
|
||
f"理由: {body.reject_reason}"
|
||
)
|
||
|
||
suggestion = await service.reject_suggestion(
|
||
db, suggestion_id, current_user.employee_id, body.reject_reason
|
||
)
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "已拒绝该建议",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 改写提案(Tier1 新增 — D7 内联审批改写)
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions/{id}/rewrite
|
||
@router.post("/suggestions/{suggestion_id}/rewrite")
|
||
@require_admin
|
||
async def rewrite_suggestion(
|
||
suggestion_id: str,
|
||
body: KnowledgeSuggestionRewrite,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""训练师改写知识库优化建议(Tier1 新增)。
|
||
|
||
改写后提案状态重置为 pending,重新走审批流程。
|
||
可修改字段:title、content、category、tags、confidence、audience、
|
||
issue、action、relation_type、parent_issue。
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(
|
||
f"管理员 {current_user.name} 改写建议: {suggestion_id}"
|
||
)
|
||
|
||
# 将非 None 的字段收集为改写数据
|
||
rewrite_data = body.model_dump(exclude_none=True, exclude_unset=True)
|
||
|
||
suggestion = await service.rewrite_suggestion(
|
||
db, suggestion_id, current_user.employee_id, rewrite_data
|
||
)
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "提案已改写,等待重新审批",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 放入独立队列(Tier1 新增 — D7 独立队列)
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions/{id}/queue
|
||
@router.post("/suggestions/{suggestion_id}/queue")
|
||
@require_admin
|
||
async def queue_suggestion(
|
||
suggestion_id: str,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""将建议放入独立审批队列(Tier1 新增)。
|
||
|
||
当会话关闭且提案仍处于 pending 时调用,将提案状态改为 queued。
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(
|
||
f"管理员 {current_user.name} 将建议放入独立队列: {suggestion_id}"
|
||
)
|
||
|
||
suggestion = await service.queue_suggestion(db, suggestion_id)
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在或状态转换无效", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "建议已放入独立审批队列",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 队列中审批通过(Tier1 新增 — D7 独立队列审批)
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions/{id}/dequeue-approve
|
||
@router.post("/suggestions/{suggestion_id}/dequeue-approve")
|
||
@require_admin
|
||
async def dequeue_approve_suggestion(
|
||
suggestion_id: str,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""从独立队列中审批通过建议(Tier1 新增)。
|
||
|
||
流程与 approve 一致:状态流转 + KB 落库 + Neo4j 写图。
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(
|
||
f"管理员 {current_user.name} 从队列中审批通过建议: {suggestion_id}"
|
||
)
|
||
|
||
neo4j_client = await get_neo4j_client()
|
||
|
||
suggestion = await service.dequeue_approve(
|
||
db, suggestion_id, current_user.employee_id,
|
||
neo4j_client=neo4j_client,
|
||
)
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在或状态转换无效", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "队列审批通过,建议已应用到知识库并同步至知识图谱",
|
||
"data": KnowledgeSuggestionResponse.model_validate(suggestion),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 获取统计
|
||
# -----------------------------------------------------------------------------
|
||
# GET /api/admin/knowledge-iteration/stats
|
||
@router.get("/stats")
|
||
@require_admin
|
||
async def get_stats(
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""获取知识库优化建议统计。
|
||
|
||
返回各状态的建议数量统计(含 queued/graph_synced/expired)。
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
stats = await service.get_suggestion_stats(db)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": KnowledgeSuggestionStatsResponse(**stats),
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 知识图谱可视化(任务2:P2)
|
||
# -----------------------------------------------------------------------------
|
||
# GET /api/admin/knowledge-iteration/graph
|
||
@router.get("/graph")
|
||
@require_admin
|
||
async def get_knowledge_graph(
|
||
limit: int = Query(default=100, ge=10, le=500, description="节点数量上限"),
|
||
issue_name: Optional[str] = Query(default=None, description="指定Issue名称查询子图"),
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""获取知识图谱数据(Neo4j 节点+关系 JSON 格式)。
|
||
|
||
返回 ECharts 力导向图兼容的节点和关系数据。
|
||
支持全图查询(默认)和指定 Issue 的子图查询。
|
||
|
||
- **limit**: 节点数量上限(10-500)
|
||
- **issue_name**: 指定 Issue 名称时查询子图(用于审批卡片预览)
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
neo4j_client = await get_neo4j_client()
|
||
|
||
if not neo4j_client:
|
||
return {
|
||
"code": 0,
|
||
"message": "Neo4j 不可用,图数据为空",
|
||
"data": {"nodes": [], "links": []},
|
||
}
|
||
|
||
if issue_name:
|
||
graph_data = await neo4j_client.query_issue_subgraph(
|
||
issue_name=issue_name, depth=1
|
||
)
|
||
else:
|
||
graph_data = await neo4j_client.query_full_graph(limit=limit)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": graph_data,
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 检查重复建议(任务3:P2 知识去重)
|
||
# -----------------------------------------------------------------------------
|
||
# GET /api/admin/knowledge-iteration/suggestions/{id}/duplicates
|
||
@router.get("/suggestions/{suggestion_id}/duplicates")
|
||
@require_admin
|
||
async def check_duplicates(
|
||
suggestion_id: str,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""检查指定建议是否存在重复(利用 Neo4j 图结构 + SQL 文本相似)。
|
||
|
||
在采纳建议前调用,检测是否有同名 Issue 或相似标题的已有条目。
|
||
返回重复项列表供训练师参考。
|
||
|
||
- **suggestion_id**: 建议ID
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
from sqlalchemy import select
|
||
|
||
stmt = select(KnowledgeSuggestion).where(
|
||
KnowledgeSuggestion.id == suggestion_id
|
||
)
|
||
result = await db.execute(stmt)
|
||
suggestion = result.scalar_one_or_none()
|
||
|
||
if not suggestion:
|
||
return {"code": 404, "message": "建议不存在", "data": None}
|
||
|
||
neo4j_client = await get_neo4j_client()
|
||
|
||
duplicates = await service.find_duplicates(
|
||
db=db,
|
||
issue_name=suggestion.issue,
|
||
title=suggestion.title,
|
||
suggestion_id=suggestion_id,
|
||
neo4j_client=neo4j_client,
|
||
)
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "success",
|
||
"data": {
|
||
"suggestion_id": suggestion_id,
|
||
"has_duplicates": len(duplicates) > 0,
|
||
"duplicates": duplicates,
|
||
},
|
||
}
|
||
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# 合并重复建议(任务3:P2 知识去重)
|
||
# -----------------------------------------------------------------------------
|
||
# POST /api/admin/knowledge-iteration/suggestions/{id}/merge
|
||
@router.post("/suggestions/{suggestion_id}/merge")
|
||
@require_admin
|
||
async def merge_suggestions(
|
||
suggestion_id: str,
|
||
body: KnowledgeSuggestionMerge,
|
||
current_user: UserInfo = Depends(get_current_user),
|
||
db: AsyncSession = Depends(get_db),
|
||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||
):
|
||
"""合并重复建议(去重操作)。
|
||
|
||
将 duplicate_id 的建议合并到当前建议(primary),
|
||
标签和元数据合并,重复建议标记为 rejected(合并归入)。
|
||
|
||
- **suggestion_id**: 主建议ID(保留)
|
||
- **duplicate_id**: 重复建议ID(将被合并)
|
||
|
||
**需要管理员权限。**
|
||
"""
|
||
logger.info(
|
||
f"管理员 {current_user.name} 合并建议: "
|
||
f"primary={suggestion_id}, duplicate={body.duplicate_id}"
|
||
)
|
||
|
||
merged = await service.merge_suggestions(
|
||
db=db,
|
||
primary_id=suggestion_id,
|
||
duplicate_id=body.duplicate_id,
|
||
reviewer_id=current_user.employee_id,
|
||
)
|
||
|
||
if not merged:
|
||
return {"code": 404, "message": "主建议不存在", "data": None}
|
||
|
||
return {
|
||
"code": 0,
|
||
"message": "建议合并完成,重复建议已标记为已驳回(合并归入)",
|
||
"data": KnowledgeSuggestionResponse.model_validate(merged),
|
||
}
|