"""知识缺口看板路由(FR-ADM-05):被问到却没有答案的查询,按热度排序。 数据来自检索无结果时的自动埋点(T-P2-06),看板只读:它回答「下一批内容 该生产什么」,是内容排期的输入,不是待办队列。 数据访问走 retrieval 域的 ``list_gaps``(T12 开发时它有个 mappings 误用 的缺陷,当时在 admin 内自实现了等价查询;上游已在合并阶段修复,现切回, 缺口语义只有一处定义)。 """ from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, Query from apps.admin.deps import SessionDep, admin_auth from apps.admin.schemas import GapOut from packages.retrieval import service as retrieval_service from packages.shared.response import ok router = APIRouter(prefix="/kb", tags=["admin:gaps"], dependencies=[Depends(admin_auth)]) @router.get("/gaps", summary="知识缺口列表(hit_count 降序:最常被问到的排最前)") async def list_gaps( session: SessionDep, limit: int = Query(default=50, ge=1, le=200), offset: int = Query(default=0, ge=0), ) -> dict[str, Any]: summaries = await retrieval_service.list_gaps(session, limit=limit, offset=offset) items = [ GapOut( gap_id=str(g.gap_id), query_text=g.query_text, filter_ctx=dict(g.filter_ctx), hit_count=g.hit_count, first_seen_at=g.first_seen_at, last_seen_at=g.last_seen_at, ).model_dump() for g in summaries ] return ok({"items": items, "total": len(items), "limit": limit, "offset": offset})