feat(backend): knowledge iteration + vision + neo4j + response contract source
dependencies.py 拆分为 dependencies/ 包; 新增 vision/ragflow_ingestion/neo4j 客户端与 h5_ai_task; alembic 045 图置信度迁移; 响应契约统一收尾。
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
# =============================================================================
|
||||
# 企微IT智能服务台 — 独立审批队列 API(Tier1 新增)
|
||||
# =============================================================================
|
||||
# 说明:独立审批队列接口,管理超出会话上下文的待审批提案。
|
||||
# 1. GET /queued — 获取队列中的提案列表
|
||||
# 2. GET /queued/stats — 获取队列统计
|
||||
# 3. POST /queued/{id}/dequeue-approve — 队列中审批通过提案
|
||||
#
|
||||
# D7 硬约束:
|
||||
# - 提案默认 status=pending,不自动 applied
|
||||
# - 未处理的进入独立队列(queued)
|
||||
# - 72 小时超时 → expired
|
||||
# =============================================================================
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy import select, func
|
||||
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.knowledge_suggestion import KnowledgeSuggestionResponse
|
||||
from app.schemas.enums import SuggestionStatusEnum
|
||||
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()
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 获取独立队列列表(Tier1 新增)
|
||||
# -----------------------------------------------------------------------------
|
||||
# GET /api/admin/approval-queue/queued
|
||||
@router.get("/queued")
|
||||
@require_admin
|
||||
async def list_queued_suggestions(
|
||||
status: Optional[str] = Query(
|
||||
default=None,
|
||||
description="筛选状态:pending/queued(不传则返回 pending+queued)",
|
||||
),
|
||||
audience: Optional[str] = Query(
|
||||
default=None,
|
||||
description="筛选受众:employee_quick_reply/engineer_workguide",
|
||||
),
|
||||
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),
|
||||
):
|
||||
"""获取独立队列中的提案列表。
|
||||
|
||||
默认返回 status=pending 和 status=queued 的提案。
|
||||
支持按 audience 筛选和分页。
|
||||
|
||||
- **status**: 筛选状态(pending/queued)
|
||||
- **audience**: 按受众类型筛选
|
||||
- **page**: 页码
|
||||
- **page_size**: 每页数量
|
||||
|
||||
**需要管理员权限。**
|
||||
"""
|
||||
# 构建查询:pending 或 queued 状态的提案
|
||||
target_statuses = [status] if status else [
|
||||
SuggestionStatusEnum.pending.value,
|
||||
SuggestionStatusEnum.queued.value,
|
||||
]
|
||||
|
||||
stmt = (
|
||||
select(KnowledgeSuggestion)
|
||||
.where(KnowledgeSuggestion.status.in_(target_statuses))
|
||||
.order_by(
|
||||
# 按入队时间降序(queued 的提案在前),然后按创建时间
|
||||
KnowledgeSuggestion.queued_at.desc().nullslast(),
|
||||
KnowledgeSuggestion.created_at.desc(),
|
||||
)
|
||||
)
|
||||
|
||||
if audience:
|
||||
stmt = stmt.where(KnowledgeSuggestion.audience == audience)
|
||||
|
||||
# 分页
|
||||
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)
|
||||
.where(KnowledgeSuggestion.status.in_(target_statuses))
|
||||
)
|
||||
if audience:
|
||||
count_stmt = count_stmt.where(KnowledgeSuggestion.audience == audience)
|
||||
|
||||
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
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 获取队列统计(Tier1 新增)
|
||||
# -----------------------------------------------------------------------------
|
||||
# GET /api/admin/approval-queue/queued/stats
|
||||
@router.get("/queued/stats")
|
||||
@require_admin
|
||||
async def get_queue_stats(
|
||||
current_user: UserInfo = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||||
):
|
||||
"""获取独立审批队列统计信息。
|
||||
|
||||
返回:
|
||||
- queued_total: 队列中提案数
|
||||
- pending_total: 待审核提案数
|
||||
- by_audience: 按受众分组统计
|
||||
- by_source_type: 按来源分组统计
|
||||
|
||||
**需要管理员权限。**
|
||||
"""
|
||||
stats = await service.get_queue_stats(db)
|
||||
|
||||
# 补充按来源分组统计
|
||||
source_stats_stmt = (
|
||||
select(
|
||||
KnowledgeSuggestion.source_type,
|
||||
func.count(),
|
||||
)
|
||||
.where(
|
||||
KnowledgeSuggestion.status.in_([
|
||||
SuggestionStatusEnum.pending.value,
|
||||
SuggestionStatusEnum.queued.value,
|
||||
])
|
||||
)
|
||||
.group_by(KnowledgeSuggestion.source_type)
|
||||
)
|
||||
source_result = await db.execute(source_stats_stmt)
|
||||
by_source_type = {row[0]: row[1] for row in source_result.fetchall()}
|
||||
|
||||
stats["by_source_type"] = by_source_type
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": stats,
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# 队列中审批通过(Tier1 新增)
|
||||
# -----------------------------------------------------------------------------
|
||||
# POST /api/admin/approval-queue/queued/{id}/dequeue-approve
|
||||
@router.post("/queued/{suggestion_id}/dequeue-approve")
|
||||
@require_admin
|
||||
async def dequeue_approve(
|
||||
suggestion_id: str,
|
||||
current_user: UserInfo = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
service: KnowledgeIterationService = Depends(dep_knowledge_iteration_service),
|
||||
):
|
||||
"""从独立队列中审批通过提案。
|
||||
|
||||
流程:queued → approved → applied → graph_synced(同 approve_suggestion)。
|
||||
|
||||
- **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),
|
||||
}
|
||||
Reference in New Issue
Block a user