T12 集成实测暴露:select(KbGap) 的结果调 .mappings(),行键是实体类, GapSummary 按列名取值必然 AttributeError。改为 .scalars() 返回实体; admin 的缺口看板从临时自实现切回 retrieval.list_gaps(语义单一定义)。
44 lines
1.6 KiB
Python
44 lines
1.6 KiB
Python
"""知识缺口看板路由(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})
|