apps/admin 独立应用:X-Admin-Token 鉴权(空=全部路由不注册=404)、实体/规则
维护与状态机流转、发布编排(publish/archive → 规则缓存 invalidate +
retrieval invalidate_cache 双失效)、素材登记与审核(tryon_ready 门禁透传)、
缺口看板(T-P2-09,FR-ADM-01~05)。V1 共享令牌鉴权,无角色无审计字段,
操作留证靠 structlog request_id。
测试:单测 tests/unit/test_admin_app.py(10 例:空令牌 404、错/缺/非 ASCII
令牌 40100、正确令牌 200 假服务、发布编排接线、幂等重放无副作用);
集成 tests/integration/test_admin_console.py(5 例:顾问全流程不碰客户端
API、双失效副作用可观察、非法跃迁 40900、素材审核门禁、缺口看板排序、
令牌 401)。483 单测 + 43 集成全绿;ruff/mypy/lint-imports 全绿。
偏差说明:
1. packages/retrieval.repo.list_gaps 有缺陷:对 select(KbGap) 的结果调
.mappings(),行键是实体名而非列名,GapSummary(row) 取 row.gap_id 必然
AttributeError(该路径无既有测试覆盖,接真库即暴露)。packages/** 本
任务禁改,故缺口查询在 apps/admin/routers/gaps.py 以等价 SQL 自实现
(hit_count 降序、同频次按 last_seen_at 降序、limit/offset 语义一致);
建议合并阶段修复上游后切回 retrieval.list_gaps。
2. 实体 transition 的 target 收全量四态(任务文档示例仅列 reviewing/
published/archived):按任务测试要求 published→draft 必须返回 40900
(状态机语义冲突)而非 schema 422;draft 作为目标由服务层跃迁矩阵恒拒。
3. 规则路由以 rule_key 为路径参数(任务文档写 /rules/{id},但 RuleService
全程以 rule_key 寻址,无按数字 id 的接口,以代码为准)。
4. 发布编排触发面含 archived(撤稿):依据 retrieval.invalidate_cache 的
自身契约「发布/归档知识后调用」,比文档字面的「发布成功后」多了撤稿
方向;reviewing 等其余状态不触发。
5. updated_by/author 记 "admin-console":V1 共享令牌无操作者身份,字段
留渠道来源;运营账号体系属 V2(见包 docstring 的升级路径)。
79 lines
2.6 KiB
Python
79 lines
2.6 KiB
Python
"""admin 域请求/响应模型(FR-ADM-01~05)。
|
||
|
||
实体与素材的载荷复用 ``packages/kb/schemas.py``(服务层契约同源);本模块只
|
||
放后台特有的形状:状态机流转请求、规则对外视图、缺口看板行。雪花 ID 一律
|
||
以字符串出现在载荷里(与 kb 契约一致:JS Number 只有 2^53 精度)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from datetime import datetime
|
||
from typing import Any, Literal
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
from packages.kb.schemas import EntityStatus
|
||
from packages.rules.dsl import Rule as RuleUpsert
|
||
|
||
__all__ = [
|
||
"GapOut",
|
||
"RuleOut",
|
||
"RuleTransitionRequest",
|
||
"RuleUpsert",
|
||
"TransitionRequest",
|
||
]
|
||
|
||
|
||
class TransitionRequest(BaseModel):
|
||
"""``POST /kb/items/{id}/transition`` 载荷。
|
||
|
||
``target`` 收全量四态而不是只收三个「前进方向」:published→draft 这类非法
|
||
跃迁应该是**状态机语义冲突(40900)**,而不是 schema 层的 422——顾问在
|
||
界面上看到的错误要能告诉他「为什么不行」(矩阵见 ``packages.kb.service``),
|
||
而不是「这个值不合法」。draft 作为目标在矩阵里永远非法,自然会被拦下。
|
||
"""
|
||
|
||
target: EntityStatus
|
||
|
||
|
||
class RuleTransitionRequest(BaseModel):
|
||
"""``POST /rules/{rule_key}/transition`` 载荷。
|
||
|
||
规则状态机里 draft 永远不是合法目标(发布与下线是仅有的两个前进动作,
|
||
与实体不同,规则没有 reviewing 中间态),直接在 schema 层收窄。
|
||
"""
|
||
|
||
target: Literal["published", "archived"]
|
||
|
||
|
||
class RuleOut(BaseModel):
|
||
"""规则对外视图。conditions/effects 保持入库的 DSL 原样(决策表是数据,
|
||
后台表单按 DSL 渲染,不做二次加工)。"""
|
||
|
||
rule_id: str
|
||
rule_key: str
|
||
category: str
|
||
version: int = Field(description="内容版本号,每次修改 +1")
|
||
status: str
|
||
priority: int
|
||
conditions: dict[str, Any]
|
||
effects: dict[str, Any]
|
||
is_hard: bool
|
||
confidence: float
|
||
evidence: str | None
|
||
author: str | None
|
||
reviewer: str | None
|
||
published_at: datetime | None
|
||
created_at: datetime
|
||
|
||
|
||
class GapOut(BaseModel):
|
||
"""缺口看板一行(kb_gap,T-P2-06)。hit_count 是排序依据也是优先级信号。"""
|
||
|
||
gap_id: str
|
||
query_text: str = Field(description="用户真实查询原文——内容生产的起点")
|
||
filter_ctx: dict[str, Any] = Field(description="缺口出现时的过滤条件快照")
|
||
hit_count: int = Field(description="同一缺口累计出现次数")
|
||
first_seen_at: datetime
|
||
last_seen_at: datetime
|