fix(analytics): 命中处置ids上限+disposition枚举校验+排序断言
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ from typing import Annotated, Any, Literal
|
||||
|
||||
import sqlalchemy as sa
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.analytics import health as health_svc
|
||||
@@ -150,20 +150,24 @@ async def delete_word(session: SessionDep, word_id: int) -> dict:
|
||||
|
||||
|
||||
class DisposeIn(BaseModel):
|
||||
ids: list[int]
|
||||
# 巨 IN 列表会撞 MySQL max_allowed_packet 裸 500 → pydantic 层拦成干净 422
|
||||
ids: list[int] = Field(max_length=1000)
|
||||
disposition: str
|
||||
|
||||
|
||||
DispositionParam = Literal["pending", "confirmed", "false_positive", "ignored"]
|
||||
|
||||
|
||||
@router.get("/sensitive/hits")
|
||||
async def list_hits(
|
||||
session: SessionDep,
|
||||
disposition: str | None = None,
|
||||
disposition: DispositionParam | None = None,
|
||||
word_id: int | None = None,
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
) -> dict:
|
||||
"""C1 命中列表——不传 disposition 默认只回 pending+confirmed(治理闭环:
|
||||
处置完即出队列);word_id 供词详情下钻。"""
|
||||
处置完即出队列);word_id 供词详情下钻。disposition 拼错 422 不静默空列表。"""
|
||||
return await hits_svc.list_hits(
|
||||
session, disposition=disposition, word_id=word_id,
|
||||
page=page, page_size=page_size,
|
||||
|
||||
@@ -201,6 +201,14 @@ async def test_hits_list_default_filter_and_dispose(make_client):
|
||||
session.add(SensitiveHit(word_id=w.id, msg_id="m2", channel="wechat",
|
||||
ts=1756900001000, snippet="…私单…",
|
||||
disposition="ignored"))
|
||||
# 排序/word_id 过滤双断言用:同 disposition 两行、不同词、ts 拉开
|
||||
w2 = await words_svc.create_word(session, word="飞单")
|
||||
session.add(SensitiveHit(word_id=w.id, msg_id="m3", channel="wechat",
|
||||
ts=1756900005000, snippet="…",
|
||||
disposition="false_positive"))
|
||||
session.add(SensitiveHit(word_id=w2.id, msg_id="m4", channel="wechat",
|
||||
ts=1756900002000, snippet="…",
|
||||
disposition="false_positive"))
|
||||
await session.commit()
|
||||
|
||||
client = await make_client(seed)
|
||||
@@ -217,3 +225,16 @@ async def test_hits_list_default_filter_and_dispose(make_client):
|
||||
r4 = await client.get("/api/analytics/sensitive/hits",
|
||||
params={"disposition": "confirmed"})
|
||||
assert r4.json()["total"] == 1
|
||||
# ts 降序:m3(ts 更大)在前;m4 属他词被 word_id 过滤掉
|
||||
r5 = await client.get("/api/analytics/sensitive/hits",
|
||||
params={"disposition": "false_positive"})
|
||||
fp = r5.json()
|
||||
assert fp["total"] == 2
|
||||
assert [h["msg_id"] for h in fp["items"]] == ["m3", "m4"]
|
||||
r6 = await client.get("/api/analytics/sensitive/hits",
|
||||
params={"disposition": "false_positive",
|
||||
"word_id": fp["items"][0]["word_id"]})
|
||||
assert r6.json()["total"] == 1
|
||||
r7 = await client.get("/api/analytics/sensitive/hits",
|
||||
params={"disposition": "bogus"})
|
||||
assert r7.status_code == 422 # 拼错值响亮 422,不静默空列表
|
||||
|
||||
Reference in New Issue
Block a user