"""规则路由(FR-ADM-02):决策表维护 + 发布/下线状态机。 规则以 ``rule_key``(命中链锚点)为地址,不是数字主键——路径参数即 rule_key。 HTTP 载荷直接复用 ``packages.rules.dsl.Rule``(封闭词汇表在入口就拦住 typo 键名),service 层落库前还会再校验一遍。发布与下线都走缓存失效编排:规则 既影响建议输出(规则缓存),也影响检索加权(rule_boost)。 """ from __future__ import annotations from typing import Any from fastapi import APIRouter, Depends, Query from apps.admin.deps import RuleServiceDep, admin_auth from apps.admin.publishing import invalidate_downstream_caches from apps.admin.schemas import RuleOut, RuleTransitionRequest, RuleUpsert from packages.shared.models import KbRule from packages.shared.response import ok router = APIRouter(prefix="/rules", tags=["admin:rules"], dependencies=[Depends(admin_auth)]) #: V1 共享令牌鉴权没有操作者身份,author 记渠道来源。 ADMIN_OPERATOR = "admin-console" def _serialize(row: KbRule) -> dict[str, Any]: return RuleOut( rule_id=str(row.rule_id), rule_key=row.rule_key, category=row.category, version=row.version, status=row.status, priority=row.priority, conditions=dict(row.conditions), effects=dict(row.effects), is_hard=row.is_hard, confidence=float(row.confidence), evidence=row.evidence, author=row.author, reviewer=row.reviewer, published_at=row.published_at, created_at=row.created_at, ).model_dump() @router.get("", summary="规则列表(可选 status 过滤)") async def list_rules( rules: RuleServiceDep, status: str | None = Query(default=None, description="draft / published / archived"), ) -> dict[str, Any]: rows = await rules.list(status) return ok({"items": [_serialize(row) for row in rows], "total": len(rows)}) @router.post("", summary="新建规则(draft / version=1 起步,DSL 先校验后落库)") async def create_rule(payload: RuleUpsert, rules: RuleServiceDep) -> dict[str, Any]: row = await rules.create(payload.model_dump(), author=ADMIN_OPERATOR) return ok(_serialize(row)) @router.patch("/{rule_key}", summary="修改规则内容(仅 draft;rule_key 不可改)") async def update_rule(rule_key: str, payload: RuleUpsert, rules: RuleServiceDep) -> dict[str, Any]: row = await rules.update(rule_key, payload.model_dump()) return ok(_serialize(row)) @router.post( "/{rule_key}/transition", summary="发布 / 下线规则(均触发规则缓存与检索缓存的失效编排)", ) async def transition_rule( rule_key: str, payload: RuleTransitionRequest, rules: RuleServiceDep ) -> dict[str, Any]: # service 的 publish/archive 自带非法跃迁校验(40900)与规则缓存失效; # 检索缓存的联动在这里补上(跨域编排属应用层职责,见 publishing 模块)。 if payload.target == "published": row = await rules.publish(rule_key) else: row = await rules.archive(rule_key) invalidate_downstream_caches(domain="kb_rule", key=rule_key, target=payload.target) return ok(_serialize(row))