feat(作者经验): 复利机制批次1——参考挑选反馈种类落账、挑选信号聚合与接入三端

This commit is contained in:
zizi 2026-09-18 23:15:55 +08:00
parent cdfb1857fe
commit 5dec38f14a
13 changed files with 2219 additions and 1244 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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"])}
)

View File

@ -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(),"

View File

@ -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__ = [
"偏好决定",
"反馈版本",
"反馈请求",
"参考挑选反馈请求",
"挑选候选",
"保存参考挑选",
"读取挑选信号",
"经验错误",
"读取偏好材料",
]

View File

@ -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

View File

@ -115,6 +115,8 @@ def _解析参数(argv: list[str] | None) -> argparse.Namespace:
"运行观察",
"运行观察清单",
"重复问题",
"记录参考挑选",
"挑选信号",
],
)
经验解析.add_argument("目标", nargs="?", help="改进身份或完整请求JSON")

View File

@ -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, 启停输入):

View File

@ -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, 作者)

View File

@ -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, 领域=领域)

View 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}],
}
)

View File

@ -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;结构升级为明确的已完成状态夹具,不代替作者升级旅程",

View 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 呈现挑选

File diff suppressed because it is too large Load Diff