feat(作者经验): 复利机制批次1——参考挑选反馈种类落账、挑选信号聚合与接入三端
This commit is contained in:
parent
cdfb1857fe
commit
5dec38f14a
File diff suppressed because it is too large
Load Diff
@ -3,7 +3,13 @@
|
||||
from dataclasses import asdict
|
||||
|
||||
from muse.作者经验.存储 import 经验存储
|
||||
from muse.作者经验.模型 import 反馈版本, 反馈请求, 经验错误
|
||||
from muse.作者经验.模型 import (
|
||||
参考挑选反馈请求,
|
||||
反馈版本,
|
||||
反馈请求,
|
||||
挑选候选,
|
||||
经验错误,
|
||||
)
|
||||
from muse.正文写作.接口 import 可见文本, 读取候选依据, 读取正文依据
|
||||
|
||||
|
||||
@ -38,9 +44,26 @@ def 保存反馈(连, 作者, command_id, 请求):
|
||||
return 经验存储(连).写反馈(作者, command_id, asdict(请求))
|
||||
|
||||
|
||||
def 保存参考挑选(连, 作者, command_id, 请求: 参考挑选反馈请求):
|
||||
"""参考挑选只进数值信号轨;结构约束在请求模型,不跨模块核验版本存在性。"""
|
||||
if not command_id.strip():
|
||||
raise 经验错误("EXPERIENCE_INVALID", "反馈命令身份不能为空")
|
||||
return 经验存储(连).写反馈(作者, command_id, asdict(请求))
|
||||
|
||||
|
||||
def 读取挑选信号(连, 作者, *, 领域=""):
|
||||
"""供 B09 偏好重排消费的聚合信号;只读,不生成效果判断。"""
|
||||
return 经验存储(连).读挑选信号(作者, 领域=领域)
|
||||
|
||||
|
||||
def 呈现反馈(连, 作者, fid):
|
||||
row = 经验存储(连).读反馈(作者, fid)
|
||||
data = row["payload"]
|
||||
if data.get("种类") == "参考挑选":
|
||||
请求 = 参考挑选反馈请求(
|
||||
**{**data, "candidates": tuple(挑选候选(**c) for c in data["candidates"])}
|
||||
)
|
||||
return {**row, "请求": asdict(请求)}
|
||||
request = 反馈请求(
|
||||
**{**data, "before": 反馈版本(**data["before"]), "after": 反馈版本(**data["after"])}
|
||||
)
|
||||
|
||||
@ -84,6 +84,54 @@ class 经验存储:
|
||||
raise 经验错误("IDEMPOTENCY_CONFLICT", "同一反馈命令已保存不同内容")
|
||||
return row
|
||||
|
||||
def 读挑选信号(self, author, *, 领域=""):
|
||||
"""聚合参考挑选反馈的数值信号;只读,不改台账。
|
||||
|
||||
选用/拒绝分列计数不混算;选中位次保留原始样本,
|
||||
位置偏置的修正策略由消费方(B09 重排)承担。
|
||||
"""
|
||||
行 = self.游标.execute(
|
||||
"""
|
||||
WITH ref AS (
|
||||
SELECT feedback_id, payload FROM muse_author_feedback
|
||||
WHERE author_id=%s AND payload->>'种类'='参考挑选'
|
||||
AND (%s='' OR payload->>'domain'=%s)
|
||||
), cand AS (
|
||||
SELECT feedback_id, c->>'method_version_id' AS mvid, (c->>'rank')::int AS rank
|
||||
FROM ref CROSS JOIN LATERAL jsonb_array_elements(payload->'candidates') AS c
|
||||
), sel AS (
|
||||
SELECT feedback_id, s AS mvid
|
||||
FROM ref CROSS JOIN LATERAL jsonb_array_elements_text(payload->'selected') AS s
|
||||
), rej AS (
|
||||
SELECT feedback_id, r AS mvid
|
||||
FROM ref CROSS JOIN LATERAL jsonb_array_elements_text(payload->'rejected') AS r
|
||||
)
|
||||
SELECT cand.mvid AS method_version_id,
|
||||
count(DISTINCT sel.feedback_id) AS 选用次数,
|
||||
count(DISTINCT rej.feedback_id) AS 拒绝次数,
|
||||
coalesce(
|
||||
array_agg(DISTINCT cand.rank) FILTER (WHERE sel.feedback_id IS NOT NULL),
|
||||
'{}'
|
||||
) AS 选中位次
|
||||
FROM cand
|
||||
LEFT JOIN sel ON sel.feedback_id=cand.feedback_id AND sel.mvid=cand.mvid
|
||||
LEFT JOIN rej ON rej.feedback_id=cand.feedback_id AND rej.mvid=cand.mvid
|
||||
GROUP BY cand.mvid
|
||||
HAVING count(DISTINCT sel.feedback_id) > 0 OR count(DISTINCT rej.feedback_id) > 0
|
||||
ORDER BY cand.mvid
|
||||
""",
|
||||
(author, 领域, 领域),
|
||||
).fetchall()
|
||||
return [
|
||||
{
|
||||
"method_version_id": r["method_version_id"],
|
||||
"选用次数": int(r["选用次数"]),
|
||||
"拒绝次数": int(r["拒绝次数"]),
|
||||
"选中位次": sorted(int(x) for x in r["选中位次"]),
|
||||
}
|
||||
for r in 行
|
||||
]
|
||||
|
||||
def 数据库标识(self):
|
||||
return self.连.execute(
|
||||
"SELECT current_database(),pg_postmaster_start_time(),"
|
||||
|
||||
@ -8,14 +8,16 @@ from muse.作者经验.作者偏好 import (
|
||||
冲突列表,
|
||||
读取偏好材料,
|
||||
)
|
||||
from muse.作者经验.反馈对 import 保存反馈, 呈现反馈
|
||||
from muse.作者经验.反馈对 import 保存参考挑选, 保存反馈, 呈现反馈, 读取挑选信号
|
||||
from muse.作者经验.存储 import 经验存储
|
||||
from muse.作者经验.模型 import (
|
||||
偏好决定,
|
||||
偏好范围,
|
||||
偏好请求,
|
||||
参考挑选反馈请求,
|
||||
反馈版本,
|
||||
反馈请求,
|
||||
挑选候选,
|
||||
改进定位,
|
||||
改进请求,
|
||||
经验错误,
|
||||
@ -199,6 +201,16 @@ class 作者经验服务:
|
||||
with self.数据库.连接(只读=True) as 连:
|
||||
return 呈现反馈(连, 身份.作者, feedback_id)
|
||||
|
||||
def 记录参考挑选反馈(self, 身份, 命令ID, 请求: 参考挑选反馈请求):
|
||||
self._身份(身份)
|
||||
with self.数据库.连接() as 连, 连.transaction():
|
||||
return 保存参考挑选(连, 身份.作者, 命令ID, 请求)
|
||||
|
||||
def 读取参考挑选信号(self, 身份, *, 领域=""):
|
||||
self._身份(身份)
|
||||
with self.数据库.连接(只读=True) as 连:
|
||||
return 读取挑选信号(连, 身份.作者, 领域=领域)
|
||||
|
||||
def 保存偏好(self, 身份, 命令ID, 请求: 偏好请求):
|
||||
self._身份(身份)
|
||||
return self.正式.提交变更(
|
||||
@ -296,6 +308,10 @@ __all__ = [
|
||||
"偏好决定",
|
||||
"反馈版本",
|
||||
"反馈请求",
|
||||
"参考挑选反馈请求",
|
||||
"挑选候选",
|
||||
"保存参考挑选",
|
||||
"读取挑选信号",
|
||||
"经验错误",
|
||||
"读取偏好材料",
|
||||
]
|
||||
|
||||
@ -127,6 +127,63 @@ class 反馈请求:
|
||||
_需要(self.decision in {"accept", "reject", "revise", "tie"}, "反馈决定无效")
|
||||
|
||||
|
||||
@合同(frozen=True, config=ConfigDict(extra="forbid"))
|
||||
class 挑选候选:
|
||||
"""参考挑选中的单个候选:方法版本、检索回执位次与融合得分。"""
|
||||
|
||||
method_version_id: str
|
||||
rank: Annotated[int, Field(strict=True, ge=1)]
|
||||
score: Annotated[float, Field(ge=0.0)]
|
||||
|
||||
def __post_init__(self):
|
||||
_需要(bool(self.method_version_id.strip()), "候选需要方法版本身份")
|
||||
_需要(type(self.rank) is int and self.rank > 0, "候选位次必须为正整数")
|
||||
_需要(type(self.score) in (int, float) and self.score >= 0, "候选得分必须为非负数")
|
||||
|
||||
|
||||
@合同(frozen=True, config=ConfigDict(extra="forbid"))
|
||||
class 参考挑选反馈请求:
|
||||
"""作者对方法检索结果的挑选与点踩。
|
||||
|
||||
只进入数值信号轨:不引用正文版本、不构成 before/after 对照,
|
||||
也不直接生成文本偏好。位次取自检索回执,供位置偏置标记;
|
||||
伪造的版本身份无需在写入时跨模块核验:重排只作用于当次检索
|
||||
实际候选集,不在集内的身份自然无效。
|
||||
"""
|
||||
|
||||
query: str
|
||||
domain: str
|
||||
candidates: tuple[挑选候选, ...]
|
||||
selected: tuple[str, ...] = ()
|
||||
rejected: tuple[str, ...] = ()
|
||||
work_id: str = ""
|
||||
quote: str = ""
|
||||
index_generation: int = 0
|
||||
种类: str = "参考挑选"
|
||||
|
||||
def __post_init__(self):
|
||||
_需要(self.种类 == "参考挑选", "参考挑选反馈种类固定")
|
||||
_需要(bool(self.query.strip()) and len(self.query) <= 500, "检索查询须非空且不超500字")
|
||||
_需要(bool(self.domain.strip()) and len(self.domain) <= 100, "领域标签须非空且不超100字")
|
||||
_需要(1 <= len(self.candidates) <= 50, "候选清单须在1至50条之间")
|
||||
身份 = [c.method_version_id for c in self.candidates]
|
||||
_需要(len(set(身份)) == len(身份), "候选方法版本不能重复")
|
||||
位次 = [c.rank for c in self.candidates]
|
||||
_需要(len(set(位次)) == len(位次), "候选位次不能重复")
|
||||
集合 = set(身份)
|
||||
_需要(len(set(self.selected)) == len(self.selected), "选中项不能重复")
|
||||
_需要(len(set(self.rejected)) == len(self.rejected), "拒绝项不能重复")
|
||||
_需要(all(m in 集合 for m in self.selected), "选中项必须来自候选清单")
|
||||
_需要(all(m in 集合 for m in self.rejected), "拒绝项必须来自候选清单")
|
||||
_需要(not (set(self.selected) & set(self.rejected)), "同一候选不能既选中又拒绝")
|
||||
_需要(bool(self.selected or self.rejected), "未选中也未拒绝时不落账,跳过不留痕")
|
||||
_需要(
|
||||
type(self.index_generation) is int and self.index_generation >= 0,
|
||||
"索引代次须为非负整数",
|
||||
)
|
||||
_需要(len(self.quote) <= 2000, "作者原话不超2000字")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class 改进请求:
|
||||
improvement_id: str
|
||||
|
||||
@ -115,6 +115,8 @@ def _解析参数(argv: list[str] | None) -> argparse.Namespace:
|
||||
"运行观察",
|
||||
"运行观察清单",
|
||||
"重复问题",
|
||||
"记录参考挑选",
|
||||
"挑选信号",
|
||||
],
|
||||
)
|
||||
经验解析.add_argument("目标", nargs="?", help="改进身份或完整请求JSON")
|
||||
|
||||
@ -9,7 +9,9 @@ from muse.共享.错误 import 配置错误
|
||||
from muse.接入.主会话 import 参数合同错误
|
||||
from muse.接入.创作操作 import 创作身份
|
||||
from muse.接入.经验操作 import (
|
||||
保存参考挑选反馈,
|
||||
保存运行观察,
|
||||
参考挑选输入,
|
||||
规则改进启停输入,
|
||||
规则改进输入,
|
||||
规则改进验证输入,
|
||||
@ -49,6 +51,8 @@ def 运行经验命令(app, 动作, 目标, *, 评测配置=None):
|
||||
return service.列出运行观察(identity, 任务ID=目标)
|
||||
if 动作 == "重复问题":
|
||||
return service.检索重复问题(identity, 问题键=目标)
|
||||
if 动作 == "挑选信号":
|
||||
return service.读取参考挑选信号(identity, 领域=目标 or "")
|
||||
if not 目标:
|
||||
raise 配置错误("此动作需要确切对象或请求JSON文件")
|
||||
if 动作 == "运行观察":
|
||||
@ -61,6 +65,7 @@ def 运行经验命令(app, 动作, 目标, *, 评测配置=None):
|
||||
"规则启停": 规则改进启停输入,
|
||||
"运行来源": 运行来源请求,
|
||||
"记录运行观察": 运行观察保存请求,
|
||||
"记录参考挑选": 参考挑选输入,
|
||||
"提出改进": 提出输入,
|
||||
"申请验证": 验证输入,
|
||||
"启用": 启停输入,
|
||||
@ -82,6 +87,8 @@ def 运行经验命令(app, 动作, 目标, *, 评测配置=None):
|
||||
return 读取运行来源(app, identity, request)
|
||||
if isinstance(request, 运行观察保存请求):
|
||||
return 保存运行观察(app, identity, request)
|
||||
if isinstance(request, 参考挑选输入):
|
||||
return 保存参考挑选反馈(app, identity, request)
|
||||
if isinstance(request, 提出输入):
|
||||
return service.提出改进(identity, request.proposal)
|
||||
if isinstance(request, 启停输入):
|
||||
|
||||
@ -9,9 +9,12 @@ from muse.作者经验.接口 import 偏好决定, 偏好请求, 反馈请求,
|
||||
from muse.接入.http.作者会话 import 作者依赖
|
||||
from muse.接入.创作操作 import 创作命令, 创作身份, 正式回执结果
|
||||
from muse.接入.经验操作 import (
|
||||
保存参考挑选反馈,
|
||||
保存运行观察,
|
||||
参考挑选输入,
|
||||
规则改进启停输入,
|
||||
规则改进输入,
|
||||
读取挑选信号视图,
|
||||
读取运行来源,
|
||||
运行来源请求,
|
||||
运行观察保存请求,
|
||||
@ -106,6 +109,22 @@ def 读取反馈(feedback_id: UUID, request: Request, 作者: 作者依赖):
|
||||
return svc.读取反馈(identity, str(feedback_id))
|
||||
|
||||
|
||||
@路由.post("/reference-selections", operation_id="capture_reference_selection", status_code=201)
|
||||
def 记录参考挑选(body: 参考挑选输入, request: Request, 作者: 作者依赖) -> dict:
|
||||
app = request.app.state.装配
|
||||
return 保存参考挑选反馈(app, 创作身份(作者, app.配置), body)
|
||||
|
||||
|
||||
@路由.get("/reference-selection-signals", operation_id="read_reference_selection_signals")
|
||||
def 读挑选信号(
|
||||
request: Request,
|
||||
作者: 作者依赖,
|
||||
domain: str = Query(default="", max_length=100),
|
||||
) -> list[dict]:
|
||||
app = request.app.state.装配
|
||||
return 读取挑选信号视图(app, 创作身份(作者, app.配置), 领域=domain)
|
||||
|
||||
|
||||
@路由.post("/improvements", operation_id="propose_improvement")
|
||||
def 提出改进(body: 改进输入, request: Request, 作者: 作者依赖):
|
||||
svc, identity = _服务(request, 作者)
|
||||
|
||||
@ -7,6 +7,7 @@ from uuid import UUID
|
||||
from pydantic import BaseModel, ConfigDict, Field, StrictInt
|
||||
|
||||
from muse.作者经验.接口 import (
|
||||
参考挑选反馈请求,
|
||||
改进定位,
|
||||
经验错误,
|
||||
规则启停请求,
|
||||
@ -77,3 +78,17 @@ class 规则改进启停输入(BaseModel):
|
||||
command_id: str = Field(min_length=1)
|
||||
selection: 改进定位
|
||||
request: 规则启停请求
|
||||
|
||||
|
||||
class 参考挑选输入(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
command_id: str = Field(min_length=1)
|
||||
selection: 参考挑选反馈请求
|
||||
|
||||
|
||||
def 保存参考挑选反馈(app, identity, request: 参考挑选输入):
|
||||
return app.要求作者经验().记录参考挑选反馈(identity, request.command_id, request.selection)
|
||||
|
||||
|
||||
def 读取挑选信号视图(app, identity, *, 领域=""):
|
||||
return app.要求作者经验().读取参考挑选信号(identity, 领域=领域)
|
||||
|
||||
114
tests/契约/test_参考挑选反馈合同.py
Normal file
114
tests/契约/test_参考挑选反馈合同.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""参考挑选反馈的公开请求合同:只进数值信号轨,结构约束在写入前拒绝。"""
|
||||
|
||||
import pytest
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
|
||||
from muse.作者经验.接口 import 参考挑选反馈请求, 挑选候选
|
||||
from muse.共享.错误 import Muse错误
|
||||
|
||||
|
||||
def _请求(**changes):
|
||||
base = {
|
||||
"query": "机甲近距格斗",
|
||||
"domain": "combat",
|
||||
"candidates": (
|
||||
挑选候选("mv-a", 1, 0.031),
|
||||
挑选候选("mv-b", 2, 0.028),
|
||||
挑选候选("mv-c", 3, 0.024),
|
||||
),
|
||||
"selected": ("mv-a",),
|
||||
"rejected": ("mv-c",),
|
||||
}
|
||||
return 参考挑选反馈请求(**{**base, **changes})
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10001",
|
||||
environment="离线",
|
||||
given="固定检索查询、领域与三候选清单",
|
||||
when="构造参考挑选反馈请求并核对结构约束",
|
||||
then=["选中与拒绝必须来自候选清单且互斥"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_选中与拒绝必须来自候选清单且互斥__a10001():
|
||||
assert _请求().种类 == "参考挑选"
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(selected=("mv-x",))
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(rejected=("mv-x",))
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(selected=("mv-a", "mv-a"))
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(selected=("mv-b",), rejected=("mv-b",))
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10002",
|
||||
environment="离线",
|
||||
given="固定检索查询、领域与候选清单",
|
||||
when="构造空决定、空候选、超界候选或重复身份/位次的请求",
|
||||
then=["跳过不落账;候选清单有界且不重复"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_跳过不落账且候选清单有界不重复__a10002():
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(selected=(), rejected=())
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(candidates=())
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(candidates=tuple(挑选候选(f"mv-{i}", i + 1, 0.01) for i in range(51)))
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(
|
||||
candidates=(挑选候选("mv-a", 1, 0.03), 挑选候选("mv-a", 2, 0.02)),
|
||||
)
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(
|
||||
candidates=(挑选候选("mv-a", 1, 0.03), 挑选候选("mv-b", 1, 0.02)),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10003",
|
||||
environment="离线",
|
||||
given="固定候选清单",
|
||||
when="篡改种类、置空查询或领域、给出非法代次",
|
||||
then=["种类固定为参考挑选;查询与领域必填;索引代次非负"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_种类查询领域与代次的边界__a10003():
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(种类="改写对照")
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(query=" ")
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(domain="")
|
||||
with pytest.raises(Muse错误):
|
||||
_请求(index_generation=-1)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10004",
|
||||
environment="离线",
|
||||
given="经由公开请求边界的JSON载荷",
|
||||
when="注入额外字段或篡改位次类型",
|
||||
then=["边界校验拒绝未知字段与非法类型"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_公开边界拒绝未知字段与非法类型__a10004():
|
||||
adapter = TypeAdapter(参考挑选反馈请求)
|
||||
good = {
|
||||
"query": "机甲近距格斗",
|
||||
"domain": "combat",
|
||||
"candidates": [{"method_version_id": "mv-a", "rank": 1, "score": 0.03}],
|
||||
"selected": ["mv-a"],
|
||||
}
|
||||
assert adapter.validate_python(good).种类 == "参考挑选"
|
||||
with pytest.raises(ValidationError):
|
||||
adapter.validate_python({**good, "effective": True})
|
||||
with pytest.raises(ValidationError):
|
||||
adapter.validate_python(
|
||||
{
|
||||
**good,
|
||||
"candidates": [{"method_version_id": "mv-a", "rank": "1", "score": 0.03}],
|
||||
}
|
||||
)
|
||||
191
tests/用例清单.json
191
tests/用例清单.json
@ -26905,6 +26905,197 @@
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10001",
|
||||
"environment": "离线",
|
||||
"given": "固定检索查询、领域与三候选清单",
|
||||
"when": "构造参考挑选反馈请求并核对结构约束",
|
||||
"then": [
|
||||
"选中与拒绝必须来自候选清单且互斥"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/契约/test_参考挑选反馈合同.py",
|
||||
"symbol": "test_选中与拒绝必须来自候选清单且互斥__a10001",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_参考挑选反馈合同.py::test_选中与拒绝必须来自候选清单且互斥__a10001"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10002",
|
||||
"environment": "离线",
|
||||
"given": "固定检索查询、领域与候选清单",
|
||||
"when": "构造空决定、空候选、超界候选或重复身份/位次的请求",
|
||||
"then": [
|
||||
"跳过不落账;候选清单有界且不重复"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/契约/test_参考挑选反馈合同.py",
|
||||
"symbol": "test_跳过不落账且候选清单有界不重复__a10002",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_参考挑选反馈合同.py::test_跳过不落账且候选清单有界不重复__a10002"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10003",
|
||||
"environment": "离线",
|
||||
"given": "固定候选清单",
|
||||
"when": "篡改种类、置空查询或领域、给出非法代次",
|
||||
"then": [
|
||||
"种类固定为参考挑选;查询与领域必填;索引代次非负"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/契约/test_参考挑选反馈合同.py",
|
||||
"symbol": "test_种类查询领域与代次的边界__a10003",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_参考挑选反馈合同.py::test_种类查询领域与代次的边界__a10003"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10004",
|
||||
"environment": "离线",
|
||||
"given": "经由公开请求边界的JSON载荷",
|
||||
"when": "注入额外字段或篡改位次类型",
|
||||
"then": [
|
||||
"边界校验拒绝未知字段与非法类型"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/契约/test_参考挑选反馈合同.py",
|
||||
"symbol": "test_公开边界拒绝未知字段与非法类型__a10004",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_参考挑选反馈合同.py::test_公开边界拒绝未知字段与非法类型__a10004"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10005",
|
||||
"environment": "隔离PG;实际S01/B05/B09/S02与合成HTTP,不证明文学效果",
|
||||
"given": "真实作者身份与固定参考挑选请求",
|
||||
"when": "经服务保存参考挑选反馈并重放同一命令",
|
||||
"then": [
|
||||
"反馈只追加落账且幂等;同命令不同内容拒绝"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/集成/test_参考挑选反馈.py",
|
||||
"symbol": "test_参考挑选落账幂等且同命令改内容拒绝__a10005",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_参考挑选反馈.py::test_参考挑选落账幂等且同命令改内容拒绝__a10005"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"候选环境",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10006",
|
||||
"environment": "隔离PG;实际S01/B05/B09/S02与合成HTTP,不证明文学效果",
|
||||
"given": "跨领域的多条真实参考挑选反馈",
|
||||
"when": "按全部领域与指定领域读取挑选信号",
|
||||
"then": [
|
||||
"选用与拒绝分列计数;位次保留样本;领域过滤生效"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/集成/test_参考挑选反馈.py",
|
||||
"symbol": "test_挑选信号分列计数与领域过滤__a10006",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_参考挑选反馈.py::test_挑选信号分列计数与领域过滤__a10006"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"候选环境",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10007",
|
||||
"environment": "隔离PG;实际S01/B05/B09/S02与合成HTTP,不证明文学效果",
|
||||
"given": "同一作者的改写对照反馈与参考挑选反馈",
|
||||
"when": "经统一呈现入口读取两类反馈",
|
||||
"then": [
|
||||
"按payload种类分派;存量改写对照合同不变"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/集成/test_参考挑选反馈.py",
|
||||
"symbol": "test_呈现反馈按种类分派且改写对照兼容__a10007",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_参考挑选反馈.py::test_呈现反馈按种类分派且改写对照兼容__a10007"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"候选环境",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-work-current-binding",
|
||||
"environment": "隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程",
|
||||
|
||||
114
tests/集成/test_参考挑选反馈.py
Normal file
114
tests/集成/test_参考挑选反馈.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""参考挑选反馈的真实落账、幂等、信号聚合与呈现分派兼容;无模型调用。"""
|
||||
|
||||
import pytest
|
||||
import test_作者偏好与反馈 as 偏好测试
|
||||
|
||||
from muse.作者经验.接口 import 参考挑选反馈请求, 挑选候选
|
||||
from muse.共享.错误 import Muse错误
|
||||
|
||||
pytestmark = pytest.mark.数据库
|
||||
候选环境 = 偏好测试.候选环境
|
||||
环境 = 偏好测试.环境
|
||||
|
||||
|
||||
def _请求(domain, selected, rejected, *, query="机甲近距格斗"):
|
||||
return 参考挑选反馈请求(
|
||||
query=query,
|
||||
domain=domain,
|
||||
candidates=(
|
||||
挑选候选("mv-a", 1, 0.031),
|
||||
挑选候选("mv-b", 2, 0.028),
|
||||
挑选候选("mv-c", 3, 0.024),
|
||||
),
|
||||
selected=selected,
|
||||
rejected=rejected,
|
||||
index_generation=7,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10005",
|
||||
environment="隔离PG;实际S01/B05/B09/S02与合成HTTP,不证明文学效果",
|
||||
given="真实作者身份与固定参考挑选请求",
|
||||
when="经服务保存参考挑选反馈并重放同一命令",
|
||||
then=["反馈只追加落账且幂等;同命令不同内容拒绝"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_参考挑选落账幂等且同命令改内容拒绝__a10005(环境):
|
||||
service, author, _, _ = 环境
|
||||
saved = service.记录参考挑选反馈(author, "ref-sel-1", _请求("combat", ("mv-a",), ("mv-c",)))
|
||||
assert saved["payload"]["种类"] == "参考挑选"
|
||||
assert saved["payload"]["selected"] == ["mv-a"]
|
||||
replay = service.记录参考挑选反馈(author, "ref-sel-1", _请求("combat", ("mv-a",), ("mv-c",)))
|
||||
assert replay["feedback_id"] == saved["feedback_id"]
|
||||
with pytest.raises(Muse错误) as 冲突:
|
||||
service.记录参考挑选反馈(author, "ref-sel-1", _请求("combat", ("mv-b",), ("mv-c",)))
|
||||
assert "IDEMPOTENCY_CONFLICT" in str(冲突.value) or "已保存不同内容" in str(冲突.value)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10006",
|
||||
environment="隔离PG;实际S01/B05/B09/S02与合成HTTP,不证明文学效果",
|
||||
given="跨领域的多条真实参考挑选反馈",
|
||||
when="按全部领域与指定领域读取挑选信号",
|
||||
then=["选用与拒绝分列计数;位次保留样本;领域过滤生效"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_挑选信号分列计数与领域过滤__a10006(环境):
|
||||
service, author, _, _ = 环境
|
||||
service.记录参考挑选反馈(author, "sig-1", _请求("combat", ("mv-a", "mv-b"), ("mv-c",)))
|
||||
service.记录参考挑选反馈(
|
||||
author,
|
||||
"sig-2",
|
||||
参考挑选反馈请求(
|
||||
query="机甲近距格斗",
|
||||
domain="combat",
|
||||
candidates=(挑选候选("mv-b", 1, 0.03), 挑选候选("mv-a", 2, 0.02)),
|
||||
selected=("mv-b",),
|
||||
index_generation=7,
|
||||
),
|
||||
)
|
||||
service.记录参考挑选反馈(author, "sig-3", _请求("emotion", (), ("mv-a",), query="情感克制"))
|
||||
|
||||
信号 = {条["method_version_id"]: 条 for 条 in service.读取参考挑选信号(author)}
|
||||
assert 信号["mv-a"]["选用次数"] == 1
|
||||
assert 信号["mv-a"]["拒绝次数"] == 1
|
||||
assert 信号["mv-a"]["选中位次"] == [1]
|
||||
assert 信号["mv-b"]["选用次数"] == 2
|
||||
assert 信号["mv-b"]["拒绝次数"] == 0
|
||||
assert 信号["mv-b"]["选中位次"] == [1, 2]
|
||||
assert 信号["mv-c"]["选用次数"] == 0
|
||||
assert 信号["mv-c"]["拒绝次数"] == 1
|
||||
assert 信号["mv-c"]["选中位次"] == []
|
||||
|
||||
combat = {条["method_version_id"]: 条 for 条 in service.读取参考挑选信号(author, 领域="combat")}
|
||||
assert combat["mv-a"]["拒绝次数"] == 0 # 拒绝发生在 emotion 领域,不串算
|
||||
assert combat["mv-a"]["选用次数"] == 1
|
||||
assert combat["mv-c"]["拒绝次数"] == 1
|
||||
信号情感 = service.读取参考挑选信号(author, 领域="emotion")
|
||||
emotion = {条["method_version_id"]: 条 for 条 in 信号情感}
|
||||
assert emotion["mv-a"]["选用次数"] == 0
|
||||
assert emotion["mv-a"]["拒绝次数"] == 1
|
||||
assert "mv-b" not in emotion
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10007",
|
||||
environment="隔离PG;实际S01/B05/B09/S02与合成HTTP,不证明文学效果",
|
||||
given="同一作者的改写对照反馈与参考挑选反馈",
|
||||
when="经统一呈现入口读取两类反馈",
|
||||
then=["按payload种类分派;存量改写对照合同不变"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_呈现反馈按种类分派且改写对照兼容__a10007(环境):
|
||||
service, author, _, 改写请求 = 环境
|
||||
对照 = service.记录反馈(author, "cmp-compat", 改写请求)
|
||||
assert "种类" not in 对照["payload"]
|
||||
呈现对照 = service.读取反馈(author, str(对照["feedback_id"]))
|
||||
assert 呈现对照["before_text"] and 呈现对照["after_text"]
|
||||
|
||||
挑选 = service.记录参考挑选反馈(author, "sel-compat", _请求("combat", ("mv-a",), ()))
|
||||
呈现挑选 = service.读取反馈(author, str(挑选["feedback_id"]))
|
||||
assert 呈现挑选["请求"]["种类"] == "参考挑选"
|
||||
assert 呈现挑选["请求"]["selected"] == ("mv-a",)
|
||||
assert "before_text" not in 呈现挑选
|
||||
1127
web/src/接口/生成/类型.ts
1127
web/src/接口/生成/类型.ts
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user