kb_rule DSL 解析 + 纯域求值(优先级分组/硬约束/用户禁忌压过专业推荐) + 400 条基准 + 黄金集雏形(T-P2-02,TD-10)。 实现:dsl.py 封闭词汇表 pydantic 校验(未识别键拒绝)+ match 纯逻辑 (等值/include/exclude/min-max,缺字段不命中);engine.py 纯域求值 (priority 降序、confidence 次序、is_hard avoid → hard_blocks、高优先级 avoid 剔除冲突 recommend);service.py+repo.py published 整表载入进程内 缓存 + CRUD + draft→published→archived 状态机(同 T6 语义)。 测试:DSL 解析与 match 矩阵、排序/硬约束/禁忌优先、状态机矩阵、 黄金集 11 组(含用户禁忌压过专业推荐与多规则叠加)、400 条 × 1000 次 基准(实测均值 0.26ms/次、p95 0.41ms,预算 50ms);集成 3 用例 (published 载入命中拦截、draft 不载入、DSL 写入拒绝与状态机守卫)。 单测 310 通过(+72),集成 19 通过(+3)。 偏差说明: 1. 冲突仲裁按通用优先级语义实现(高优先级 avoid 剔除"严格更低"优先级的 冲突 recommend),任务文档的"用户禁忌(≥95)永远压过专业推荐(≤90)" 是该规则的特例,黄金集已含该场景用例。 2. include 子句按字段类型取语义:标量字段=命中列表任一值;集合字段= 列表全部在场(子集,精确命中);文档未区分,此实现两类字段均可用。 3. 单测引擎文件命名为 tests/unit/test_rules_evaluation.py(而非 test_rules_engine.py):与契约指定的集成文件 tests/integration/ test_rules_engine.py 同名会导致 pytest 收集冲突(tests 目录非包)。 4. 集成测试每用例独立 engine 并 dispose:pytest-asyncio 每用例一个事件 循环,直接用全局 session 工厂会让连接跨 loop 污染其它集成用例。 5. 规则不提供物理删除(命中链需可追溯,下线走 archived);kb_rule 契约 无 reviewing 状态,状态机为 draft→published→archived(draft 可撤稿)。
44 lines
1.4 KiB
Python
44 lines
1.4 KiB
Python
"""kb_rule 表数据访问。**只有本模块读写 kb_rule**(表与 packages/kb 共享,
|
|
但规则包只经 ORM 访问,不碰 T6 的文件)。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from packages.shared.models import KbRule
|
|
|
|
STATUS_PUBLISHED = "published"
|
|
|
|
|
|
async def list_published(session: AsyncSession) -> list[KbRule]:
|
|
"""全量已发布规则。量级 ~400,直接整表载入;序即求值序(priority 降序)。"""
|
|
result = await session.execute(
|
|
select(KbRule)
|
|
.where(KbRule.status == STATUS_PUBLISHED)
|
|
.order_by(KbRule.priority.desc(), KbRule.rule_key)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def get_by_key(session: AsyncSession, rule_key: str) -> KbRule | None:
|
|
result = await session.execute(select(KbRule).where(KbRule.rule_key == rule_key))
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def list_by_status(session: AsyncSession, status: str) -> list[KbRule]:
|
|
result = await session.execute(
|
|
select(KbRule).where(KbRule.status == status).order_by(KbRule.rule_key)
|
|
)
|
|
return list(result.scalars().all())
|
|
|
|
|
|
async def list_all(session: AsyncSession) -> list[KbRule]:
|
|
result = await session.execute(select(KbRule).order_by(KbRule.rule_key))
|
|
return list(result.scalars().all())
|
|
|
|
|
|
def add_rule(session: AsyncSession, rule: KbRule) -> KbRule:
|
|
session.add(rule)
|
|
return rule
|