feat(作者经验): 复利机制批次4——确定性推断偏好建议、采纳确认链与复利度量统计
This commit is contained in:
parent
f0a61286df
commit
2d0410d053
@ -6602,6 +6602,78 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/inferred-preference-suggestions": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"作者经验"
|
||||
],
|
||||
"summary": "读推断建议",
|
||||
"operationId": "read_inferred_preference_suggestions",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "domain",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"default": "",
|
||||
"title": "Domain"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"title": "Response Read Inferred Preference Suggestions"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/compounding-metrics": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"作者经验"
|
||||
],
|
||||
"summary": "读复利度量",
|
||||
"operationId": "read_compounding_metrics",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Response Read Compounding Metrics"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/improvements": {
|
||||
"post": {
|
||||
"tags": [
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
| 核对方法评测投影 | 同用途元数据连接和已封存材料 | 重验内容/结构/字段策略哈希;评测不直接读正式方法表 |
|
||||
| 读取消费统计 | 作者、方法版本范围 | 按版本聚合的实际消费计数,供 B09 偏好重排的强信号路;只读,不改消费台账,不把被检索或任务成功当作消费 |
|
||||
| 读取参考材料 | 作者、确切方法版本清单、两种用途 | 当前且已确认或启用版本的参考材料渲染与哈希,供参考改写冻结与依赖复检;不查启用凭据、不写消费 |
|
||||
| 读取版本头 | 方法版本清单 | 版本→标题/类型/状态/是否当前的只读窄头,非法或缺失身份跳过;供 B07 推断建议渲染标题,不展开材料 |
|
||||
|
||||
所有接口在本模块的接口.py 暴露。输入输出类型由同一公开表面导出,外部不导入私有模型或 SQL。
|
||||
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
| 读取运行观察 / 列出运行观察 / 检索重复问题 | 当前作者、观察或任务身份、可选问题键 | 历史观察与按实际任务计数的重复问题,不生成效果判断 |
|
||||
| 记录参考挑选反馈 | 检索回执引用、领域标签、选中与拒绝的方法版本清单 | 只追加参考挑选反馈;进入数值信号轨,不直接生成文本偏好 |
|
||||
| 读取挑选信号 | 作者、可选领域与方法版本范围 | 选用/拒绝计数与位次聚合,供 B09 偏好重排消费;只读,不改台账 |
|
||||
| 汇总推断偏好建议 | 作者、可选领域 | 确定性聚合的只读建议(含真实原话依据与内联证据);不落库,采纳走既有保存→确认链 |
|
||||
| 读取复利度量 | 作者 | 台账统计:首稿采纳率、点踩率等;分母为零保持空值,不构成因果结论 |
|
||||
|
||||
所有接口在本模块的接口.py 暴露。输入输出类型由同一公开表面导出,外部不导入私有模型或 SQL。
|
||||
|
||||
@ -36,7 +38,8 @@
|
||||
| 模型.py | 作者经验稳定对象、外壳及内部状态 |
|
||||
| 存储.py | 作者经验所属记录的 SQL 与版本读写 |
|
||||
| 作者偏好.py | 捕获、确认、范围、冲突和撤回 |
|
||||
| 反馈对.py | 原稿、改稿、作者原话及决策 |
|
||||
| 反馈对.py | 原稿、改稿、作者原话及决策;参考挑选反馈与挑选信号读取 |
|
||||
| 推断偏好.py | 确定性推断建议聚合与复利度量统计 |
|
||||
| 观察记录.py | 真实反馈对上的作者自报观察 |
|
||||
| 运行观察.py | 实际运行来源、业务回执核对、运行观察与重复问题检索 |
|
||||
| 改进提案.py | 具体目标变更、版本及内容哈希 |
|
||||
@ -67,7 +70,7 @@
|
||||
- **文本偏好轨**(muse_preference):topic/text/scope 的人可读语句,经偏好材料注入写手提示词,由作者裁决生命周期(pending→active→withdrawn),以偏好绑定进入冻结快照。文本轨不参与检索排序。
|
||||
- **数值信号轨**(参考挑选反馈与 B04 消费统计的聚合):仅供 B09 方法检索的偏好重排产生偏好分,权重经人工审查版本化。数值轨不冒充作者偏好,不自动生成生效文本。
|
||||
|
||||
推断偏好是数值轨到文本轨的受控翻译:确定性聚合脚本(不调用模型)从信号模式产出 origin=inferred 的 pending 偏好,卡片内联证据(如"近 30 次挑选中被选 12 次"),作者显式确认后才 active,确认沿用既有冲突管理合同。护栏:单条推断需 ≥5 个支撑事件才生成;每次呈现摘要 ≤5 条;v1 不做权重自动调参——重排权重只经人工审查的版本号变更,作者主权延伸到排序策略。
|
||||
推断偏好是数值轨到文本轨的受控翻译:确定性聚合(不调用模型)从参考挑选台账生成偏好建议;建议是只读呈现面,不自动落库,采纳经既有保存→审阅→确认链成为 origin=inferred 的 pending 偏好,确认沿用既有冲突管理合同,不自动长期生效。护栏:单版本 ≥5 个支撑事件才提出;每次至多返回 5 条,按证据强度降序;建议必须携带一条真实作者反馈原话作依据,保存时校验原话与该反馈一致;v1 不做权重自动调参——重排权重只经人工审查的版本号变更,作者主权延伸到排序策略。复利度量是台账的确定性统计(首稿采纳率、点踩率等):比率分母为零保持空值不混算零,只作趋势呈现,不构成因果结论。语义归纳升级(模型抽象偏好)须另立受控提案,产物同样只进 pending。
|
||||
|
||||
### 具体方法改进与用途交接
|
||||
|
||||
|
||||
@ -132,6 +132,58 @@ class 经验存储:
|
||||
for r in 行
|
||||
]
|
||||
|
||||
def 读挑选明细(self, author, *, 领域="", 上限=5000):
|
||||
"""挑选台账的有界明细;供确定性推断聚合,不改台账。
|
||||
|
||||
每条是一次反馈对一个方法版本的动作(选用或拒绝),
|
||||
带领域、作者原话与时间;按时间倒序,超出上限只取最近部分。
|
||||
"""
|
||||
行 = self.游标.execute(
|
||||
"""
|
||||
WITH ref AS (
|
||||
SELECT feedback_id, created_at, payload FROM muse_author_feedback
|
||||
WHERE author_id=%s AND payload->>'种类'='参考挑选'
|
||||
AND (%s='' OR payload->>'domain'=%s)
|
||||
), 动作 AS (
|
||||
SELECT feedback_id, created_at, payload->>'domain' AS domain,
|
||||
payload->>'query' AS query, payload->>'quote' AS quote,
|
||||
s AS mvid, 'selected' AS action
|
||||
FROM ref CROSS JOIN LATERAL jsonb_array_elements_text(payload->'selected') AS s
|
||||
UNION ALL
|
||||
SELECT feedback_id, created_at, payload->>'domain' AS domain,
|
||||
payload->>'query' AS query, payload->>'quote' AS quote,
|
||||
r AS mvid, 'rejected' AS action
|
||||
FROM ref CROSS JOIN LATERAL jsonb_array_elements_text(payload->'rejected') AS r
|
||||
)
|
||||
SELECT feedback_id::text, created_at, domain, query, quote, mvid, action
|
||||
FROM 动作 ORDER BY created_at DESC LIMIT %s
|
||||
""",
|
||||
(author, 领域, 领域, 上限),
|
||||
).fetchall()
|
||||
return [
|
||||
{
|
||||
"feedback_id": r["feedback_id"],
|
||||
"created_at": r["created_at"],
|
||||
"domain": r["domain"],
|
||||
"query": r["query"],
|
||||
"quote": r["quote"],
|
||||
"method_version_id": r["mvid"],
|
||||
"action": r["action"],
|
||||
}
|
||||
for r in 行
|
||||
]
|
||||
|
||||
def 读改写决定统计(self, author):
|
||||
"""改写对照反馈的决定分布;存量无种类字段的记录按改写对照计。"""
|
||||
行 = self.游标.execute(
|
||||
"SELECT COALESCE(payload->>'decision','') AS decision, count(*) AS n "
|
||||
"FROM muse_author_feedback "
|
||||
"WHERE author_id=%s AND COALESCE(payload->>'种类','改写对照')='改写对照' "
|
||||
"GROUP BY 1 ORDER BY 1",
|
||||
(author,),
|
||||
).fetchall()
|
||||
return {r["decision"]: int(r["n"]) for r in 行 if r["decision"]}
|
||||
|
||||
def 数据库标识(self):
|
||||
return self.连.execute(
|
||||
"SELECT current_database(),pg_postmaster_start_time(),"
|
||||
|
||||
@ -10,6 +10,7 @@ from muse.作者经验.作者偏好 import (
|
||||
)
|
||||
from muse.作者经验.反馈对 import 保存参考挑选, 保存反馈, 呈现反馈, 读取挑选信号
|
||||
from muse.作者经验.存储 import 经验存储
|
||||
from muse.作者经验.推断偏好 import 汇总推断建议, 读取复利度量
|
||||
from muse.作者经验.模型 import (
|
||||
偏好决定,
|
||||
偏好范围,
|
||||
@ -211,6 +212,17 @@ class 作者经验服务:
|
||||
with self.数据库.连接(只读=True) as 连:
|
||||
return 读取挑选信号(连, 身份.作者, 领域=领域)
|
||||
|
||||
def 汇总推断偏好建议(self, 身份, *, 领域=""):
|
||||
"""确定性聚合的 pending 偏好建议;只读呈现,采纳走既有保存→确认链。"""
|
||||
self._身份(身份)
|
||||
with self.数据库.连接(只读=True) as 连:
|
||||
return 汇总推断建议(连, 身份.作者, 领域=领域)
|
||||
|
||||
def 读取复利度量(self, 身份):
|
||||
self._身份(身份)
|
||||
with self.数据库.连接(只读=True) as 连:
|
||||
return 读取复利度量(连, 身份.作者)
|
||||
|
||||
def 保存偏好(self, 身份, 命令ID, 请求: 偏好请求):
|
||||
self._身份(身份)
|
||||
return self.正式.提交变更(
|
||||
@ -312,6 +324,8 @@ __all__ = [
|
||||
"挑选候选",
|
||||
"保存参考挑选",
|
||||
"读取挑选信号",
|
||||
"汇总推断建议",
|
||||
"读取复利度量",
|
||||
"经验错误",
|
||||
"读取偏好材料",
|
||||
]
|
||||
|
||||
171
src/muse/作者经验/推断偏好.py
Normal file
171
src/muse/作者经验/推断偏好.py
Normal file
@ -0,0 +1,171 @@
|
||||
"""确定性推断聚合:从参考挑选台账提炼 pending 偏好建议;只读,不落库。
|
||||
|
||||
合同见模块设计/B07-作者经验.md「偏好双轨关系」:
|
||||
- 聚合是确定性脚本,不调用模型;语义归纳升级须另立受控提案。
|
||||
- 护栏:单版本支撑事件 ≥5 才生成建议;每次至多返回 5 条;
|
||||
建议必须携带一条真实反馈原话作依据(推断偏好保存时原话必须一致)。
|
||||
- 建议只是呈现面:采纳走既有 保存偏好(origin=inferred)→审阅→确认 链,
|
||||
不自动生效、不自动落库、不代替作者裁决。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from muse.作者经验.存储 import 经验存储
|
||||
|
||||
_最少支撑事件 = 5
|
||||
_建议上限 = 5
|
||||
_明细上限 = 5000
|
||||
|
||||
_领域标签 = {
|
||||
"combat": "打戏",
|
||||
"emotion": "情感",
|
||||
"dialogue": "对话",
|
||||
"inner": "内心",
|
||||
"environment": "环境",
|
||||
"style": "文风",
|
||||
"pacing": "节奏",
|
||||
"other": "其他",
|
||||
}
|
||||
|
||||
|
||||
def 聚合挑选明细(明细: list[dict]) -> dict[tuple[str, str], dict[str, Any]]:
|
||||
"""把有界明细按(领域,方法版本)聚合;纯函数,供单测护栏。"""
|
||||
聚合: dict[tuple[str, str], dict[str, Any]] = {}
|
||||
for 行 in 明细:
|
||||
键 = (行["domain"], 行["method_version_id"])
|
||||
桶 = 聚合.setdefault(
|
||||
键,
|
||||
{"选用次数": 0, "拒绝次数": 0, "选用依据": None, "拒绝依据": None},
|
||||
)
|
||||
if 行["action"] == "selected":
|
||||
桶["选用次数"] += 1
|
||||
if 桶["选用依据"] is None and 行["quote"].strip():
|
||||
桶["选用依据"] = 行 # 明细按时间倒序,首个非空原话即最近一条
|
||||
else:
|
||||
桶["拒绝次数"] += 1
|
||||
if 桶["拒绝依据"] is None and 行["quote"].strip():
|
||||
桶["拒绝依据"] = 行
|
||||
return 聚合
|
||||
|
||||
|
||||
def 生成建议(
|
||||
聚合: dict[tuple[str, str], dict[str, Any]], 标题表: dict[str, dict]
|
||||
) -> list[dict[str, Any]]:
|
||||
"""从聚合桶生成偏好建议;纯函数。
|
||||
|
||||
规则(v1 保守,两类):
|
||||
- 选用倾向:支撑事件 ≥5 且 选用 ≥ 3×拒绝,且有带原话的选用依据;
|
||||
- 回避倾向:支撑事件 ≥5 且 拒绝 ≥ 3×选用,且有带原话的拒绝依据。
|
||||
无原话依据不生成——推断偏好保存时要求原话与真实反馈一致,
|
||||
没有依据的建议无法诚实落库。按支撑事件数降序,至多 5 条。
|
||||
"""
|
||||
建议: list[dict[str, Any]] = []
|
||||
for (领域, 版本), 桶 in 聚合.items():
|
||||
选用, 拒绝 = 桶["选用次数"], 桶["拒绝次数"]
|
||||
事件 = 选用 + 拒绝
|
||||
if 事件 < _最少支撑事件:
|
||||
continue
|
||||
标签 = _领域标签.get(领域, 领域)
|
||||
头 = 标题表.get(版本) or {}
|
||||
标题 = 头.get("title", 版本)
|
||||
if 选用 >= 3 * 拒绝 and 桶["选用依据"] is not None:
|
||||
依据 = 桶["选用依据"]
|
||||
建议.append(
|
||||
{
|
||||
"topic": f"参考偏好:{领域}",
|
||||
"text": (
|
||||
f"「{标签}」领域改写优先参考「{标题}」类技法:"
|
||||
f"作者已选用 {选用} 次、拒绝 {拒绝} 次。"
|
||||
),
|
||||
"quote": 依据["quote"],
|
||||
"feedback_id": 依据["feedback_id"],
|
||||
"method_version_id": 版本,
|
||||
"domain": 领域,
|
||||
"支撑事件": 事件,
|
||||
"证据": {
|
||||
"选用次数": 选用,
|
||||
"拒绝次数": 拒绝,
|
||||
"方法标题": 标题,
|
||||
"measurement_source": "feedback_ledger",
|
||||
"causality": "not_established",
|
||||
},
|
||||
}
|
||||
)
|
||||
elif 拒绝 >= 3 * 选用 and 桶["拒绝依据"] is not None:
|
||||
依据 = 桶["拒绝依据"]
|
||||
建议.append(
|
||||
{
|
||||
"topic": f"参考偏好:{领域}",
|
||||
"text": (
|
||||
f"「{标签}」领域避免「{标题}」类技法,除非作者明确要求:"
|
||||
f"作者已拒绝 {拒绝} 次、选用 {选用} 次。"
|
||||
),
|
||||
"quote": 依据["quote"],
|
||||
"feedback_id": 依据["feedback_id"],
|
||||
"method_version_id": 版本,
|
||||
"domain": 领域,
|
||||
"支撑事件": 事件,
|
||||
"证据": {
|
||||
"选用次数": 选用,
|
||||
"拒绝次数": 拒绝,
|
||||
"方法标题": 标题,
|
||||
"measurement_source": "feedback_ledger",
|
||||
"causality": "not_established",
|
||||
},
|
||||
}
|
||||
)
|
||||
建议.sort(key=lambda 条: (-条["支撑事件"], 条["topic"], 条["method_version_id"]))
|
||||
return 建议[:_建议上限]
|
||||
|
||||
|
||||
def 汇总推断建议(连, 作者: str, *, 领域: str = "") -> list[dict[str, Any]]:
|
||||
"""读取有界挑选明细并经 B04 版本头渲染标题;确定性、只读。"""
|
||||
from muse.知识方法.接口 import 读取版本头
|
||||
|
||||
明细 = 经验存储(连).读挑选明细(作者, 领域=领域, 上限=_明细上限)
|
||||
if not 明细:
|
||||
return []
|
||||
聚合 = 聚合挑选明细(明细)
|
||||
标题表 = 读取版本头(连, tuple({版本 for _, 版本 in 聚合}))
|
||||
return 生成建议(聚合, 标题表)
|
||||
|
||||
|
||||
def 读取复利度量(连, 作者: str) -> dict[str, Any]:
|
||||
"""台账的确定性统计;比率分母为零时保持 None,不混算零。
|
||||
|
||||
只是账本事实的呈现,不构成因果或质量结论。
|
||||
"""
|
||||
存储 = 经验存储(连)
|
||||
决定 = 存储.读改写决定统计(作者)
|
||||
总改写 = sum(决定.values())
|
||||
采纳 = 决定.get("accept", 0)
|
||||
信号 = 存储.读挑选信号(作者)
|
||||
总选用 = sum(条["选用次数"] for 条 in 信号)
|
||||
总拒绝 = sum(条["拒绝次数"] for 条 in 信号)
|
||||
总挑选 = 总选用 + 总拒绝
|
||||
return {
|
||||
"改写对照": {
|
||||
**决定,
|
||||
"总数": 总改写,
|
||||
"首稿采纳率": (采纳 / 总改写) if 总改写 else None,
|
||||
},
|
||||
"参考挑选": {
|
||||
"选用次数": 总选用,
|
||||
"拒绝次数": 总拒绝,
|
||||
"点踩率": (总拒绝 / 总挑选) if 总挑选 else None,
|
||||
"有信号版本数": len(信号),
|
||||
},
|
||||
"measurement_source": "feedback_ledger",
|
||||
"causality": "not_established",
|
||||
"说明": "账本统计只作趋势参考,不构成因果结论;比率分母为零时保持空值。",
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"聚合挑选明细",
|
||||
"生成建议",
|
||||
"汇总推断建议",
|
||||
"读取复利度量",
|
||||
]
|
||||
@ -117,6 +117,8 @@ def _解析参数(argv: list[str] | None) -> argparse.Namespace:
|
||||
"重复问题",
|
||||
"记录参考挑选",
|
||||
"挑选信号",
|
||||
"推断建议",
|
||||
"复利度量",
|
||||
],
|
||||
)
|
||||
经验解析.add_argument("目标", nargs="?", help="改进身份或完整请求JSON")
|
||||
|
||||
@ -53,6 +53,10 @@ def 运行经验命令(app, 动作, 目标, *, 评测配置=None):
|
||||
return service.检索重复问题(identity, 问题键=目标)
|
||||
if 动作 == "挑选信号":
|
||||
return service.读取参考挑选信号(identity, 领域=目标 or "")
|
||||
if 动作 == "推断建议":
|
||||
return service.汇总推断偏好建议(identity, 领域=目标 or "")
|
||||
if 动作 == "复利度量":
|
||||
return service.读取复利度量(identity)
|
||||
if not 目标:
|
||||
raise 配置错误("此动作需要确切对象或请求JSON文件")
|
||||
if 动作 == "运行观察":
|
||||
|
||||
@ -14,7 +14,9 @@ from muse.接入.经验操作 import (
|
||||
参考挑选输入,
|
||||
规则改进启停输入,
|
||||
规则改进输入,
|
||||
读取复利度量视图,
|
||||
读取挑选信号视图,
|
||||
读取推断建议视图,
|
||||
读取运行来源,
|
||||
运行来源请求,
|
||||
运行观察保存请求,
|
||||
@ -125,6 +127,22 @@ def 读挑选信号(
|
||||
return 读取挑选信号视图(app, 创作身份(作者, app.配置), 领域=domain)
|
||||
|
||||
|
||||
@路由.get("/inferred-preference-suggestions", operation_id="read_inferred_preference_suggestions")
|
||||
def 读推断建议(
|
||||
request: Request,
|
||||
作者: 作者依赖,
|
||||
domain: str = Query(default="", max_length=100),
|
||||
) -> list[dict]:
|
||||
app = request.app.state.装配
|
||||
return 读取推断建议视图(app, 创作身份(作者, app.配置), 领域=domain)
|
||||
|
||||
|
||||
@路由.get("/compounding-metrics", operation_id="read_compounding_metrics")
|
||||
def 读复利度量(request: Request, 作者: 作者依赖) -> dict:
|
||||
app = request.app.state.装配
|
||||
return 读取复利度量视图(app, 创作身份(作者, app.配置))
|
||||
|
||||
|
||||
@路由.post("/improvements", operation_id="propose_improvement")
|
||||
def 提出改进(body: 改进输入, request: Request, 作者: 作者依赖):
|
||||
svc, identity = _服务(request, 作者)
|
||||
|
||||
@ -92,3 +92,11 @@ def 保存参考挑选反馈(app, identity, request: 参考挑选输入):
|
||||
|
||||
def 读取挑选信号视图(app, identity, *, 领域=""):
|
||||
return app.要求作者经验().读取参考挑选信号(identity, 领域=领域)
|
||||
|
||||
|
||||
def 读取推断建议视图(app, identity, *, 领域=""):
|
||||
return app.要求作者经验().汇总推断偏好建议(identity, 领域=领域)
|
||||
|
||||
|
||||
def 读取复利度量视图(app, identity):
|
||||
return app.要求作者经验().读取复利度量(identity)
|
||||
|
||||
@ -198,6 +198,20 @@ class 方法存储:
|
||||
(version_id,),
|
||||
).fetchone()
|
||||
|
||||
def 读取版本头集合(self, version_ids: list[str]) -> list[dict[str, Any]]:
|
||||
if not version_ids:
|
||||
return []
|
||||
行 = self._查(
|
||||
"SELECT v.version_id::text AS version_id, v.method_id::text AS method_id,"
|
||||
" v.version, m.current_version, m.state, m.type_id,"
|
||||
" COALESCE(p.request->>'title', m.title) AS title "
|
||||
"FROM muse_method_version v JOIN muse_method m USING (method_id) "
|
||||
"LEFT JOIN muse_method_proposal p ON p.proposal_id=v.proposal_id "
|
||||
"WHERE v.version_id = ANY(%s::uuid[])",
|
||||
(version_ids,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in 行]
|
||||
|
||||
def 按哈希读版本(self, method_id: str, content_hash: str) -> dict[str, Any] | None:
|
||||
return self._查(
|
||||
"SELECT version_id,version FROM muse_method_version "
|
||||
|
||||
@ -18,6 +18,7 @@ from muse.知识方法.方法卡 import (
|
||||
读取方法检索资料,
|
||||
读取方法检索身份,
|
||||
读取方法索引维护资料,
|
||||
读取版本头,
|
||||
读取版本材料大小,
|
||||
)
|
||||
from muse.知识方法.方法版本 import (
|
||||
@ -319,6 +320,7 @@ __all__ = [
|
||||
"读取方法检索资料",
|
||||
"读取方法检索候选",
|
||||
"读取参考材料",
|
||||
"读取版本头",
|
||||
"读取消费统计",
|
||||
"读取方法检索身份",
|
||||
"读取方法索引维护资料",
|
||||
|
||||
@ -202,6 +202,31 @@ def 读取方法检索候选(
|
||||
}
|
||||
|
||||
|
||||
def 读取版本头(连, version_ids) -> dict[str, dict[str, Any]]:
|
||||
"""版本→标题/类型/状态/是否当前的只读窄头;非法或缺失身份跳过,不报错。
|
||||
|
||||
供 B07 推断建议渲染方法标题;不展开材料、不写消费、不做授权判断。
|
||||
"""
|
||||
合法: list[str] = []
|
||||
for 原始 in version_ids:
|
||||
try:
|
||||
UUID(str(原始))
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
continue
|
||||
合法.append(str(原始))
|
||||
头 = {}
|
||||
for 行 in 方法存储(连).读取版本头集合(合法):
|
||||
头[行["version_id"]] = {
|
||||
"method_id": 行["method_id"],
|
||||
"version": int(行["version"]),
|
||||
"title": 行["title"],
|
||||
"type_id": 行["type_id"],
|
||||
"state": 行["state"],
|
||||
"是当前版本": int(行["version"]) == int(行["current_version"]),
|
||||
}
|
||||
return 头
|
||||
|
||||
|
||||
def 读取参考材料(
|
||||
连,
|
||||
version_ids: tuple[str, ...],
|
||||
|
||||
84
tests/单元/test_推断偏好聚合.py
Normal file
84
tests/单元/test_推断偏好聚合.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""推断偏好聚合的纯函数护栏:事件阈值、比例、原话依据、条数上限与排序。"""
|
||||
|
||||
import pytest
|
||||
|
||||
from muse.作者经验.推断偏好 import 生成建议, 聚合挑选明细
|
||||
|
||||
|
||||
def _明细(动作, 原话="", 时刻=0):
|
||||
return {
|
||||
"feedback_id": f"fb-{动作}-{时刻}",
|
||||
"created_at": 时刻,
|
||||
"domain": "combat",
|
||||
"query": "机甲",
|
||||
"quote": 原话,
|
||||
"method_version_id": "v1",
|
||||
"action": 动作,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10020",
|
||||
environment="离线",
|
||||
given="构造的挑选明细(时间倒序)",
|
||||
when="聚合明细并生成推断建议",
|
||||
then=["分列计数、依据取最近非空原话、护栏与排序生效"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_聚合与建议护栏__a10020():
|
||||
# 明细按时间倒序(与存储读法一致):最近的原话在前
|
||||
明细 = [
|
||||
_明细("selected", "最近的打戏原话", 9),
|
||||
_明细("selected", "", 8),
|
||||
_明细("selected", "较早的原话", 7),
|
||||
_明细("selected", "", 6),
|
||||
_明细("rejected", "", 5),
|
||||
]
|
||||
聚合 = 聚合挑选明细(明细)
|
||||
桶 = 聚合[("combat", "v1")]
|
||||
assert 桶["选用次数"] == 4 and 桶["拒绝次数"] == 1
|
||||
assert 桶["选用依据"]["quote"] == "最近的打戏原话"
|
||||
|
||||
标题表 = {"v1": {"title": "机甲近身刀法"}}
|
||||
建议 = 生成建议(聚合, 标题表)
|
||||
assert len(建议) == 1
|
||||
条 = 建议[0]
|
||||
assert 条["topic"] == "参考偏好:combat"
|
||||
assert "机甲近身刀法" in 条["text"] and "选用 4 次" in 条["text"]
|
||||
assert 条["quote"] == "最近的打戏原话"
|
||||
assert 条["feedback_id"] == "fb-selected-9"
|
||||
assert 条["证据"]["causality"] == "not_established"
|
||||
|
||||
# 护栏1:支撑事件不足 5 不生成
|
||||
assert 生成建议(聚合挑选明细(明细[:3]), 标题表) == []
|
||||
# 护栏2:选用未达 3 倍拒绝不生成(3选2拒=5事件但 3 < 3*2)
|
||||
胶着 = [
|
||||
_明细("selected", "原话", 5),
|
||||
_明细("selected", "", 4),
|
||||
_明细("selected", "", 3),
|
||||
_明细("rejected", "", 2),
|
||||
_明细("rejected", "", 1),
|
||||
]
|
||||
assert 生成建议(聚合挑选明细(胶着), 标题表) == []
|
||||
# 护栏3:无原话依据不生成
|
||||
无原话 = [_明细("selected", "", i) for i in range(5)]
|
||||
assert 生成建议(聚合挑选明细(无原话), 标题表) == []
|
||||
# 回避倾向:拒绝 ≥5 且 ≥3 倍选用,带拒绝原话
|
||||
回避 = [_明细("rejected", "不要这种花哨写法", 9)] + [_明细("rejected", "", i) for i in range(4)]
|
||||
回避建议 = 生成建议(聚合挑选明细(回避), 标题表)
|
||||
assert len(回避建议) == 1 and "避免" in 回避建议[0]["text"]
|
||||
assert 回避建议[0]["quote"] == "不要这种花哨写法"
|
||||
|
||||
# 护栏4:至多 5 条,按支撑事件降序
|
||||
多版本 = {}
|
||||
for 序 in range(7):
|
||||
vid = f"v{序}"
|
||||
行 = [
|
||||
{**_明细("selected", f"原话{序}", 时刻), "method_version_id": vid}
|
||||
for 时刻 in range(5 + 序)
|
||||
]
|
||||
多版本.update(聚合挑选明细(行))
|
||||
全部 = 生成建议(多版本, {f"v{序}": {"title": f"方法{序}"} for 序 in range(7)})
|
||||
assert len(全部) == 5
|
||||
事件数 = [条["支撑事件"] for 条 in 全部]
|
||||
assert 事件数 == sorted(事件数, reverse=True)
|
||||
@ -27431,6 +27431,95 @@
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10020",
|
||||
"environment": "离线",
|
||||
"given": "构造的挑选明细(时间倒序)",
|
||||
"when": "聚合明细并生成推断建议",
|
||||
"then": [
|
||||
"分列计数、依据取最近非空原话、护栏与排序生效"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/单元/test_推断偏好聚合.py",
|
||||
"symbol": "test_聚合与建议护栏__a10020",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/单元/test_推断偏好聚合.py::test_聚合与建议护栏__a10020"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10021",
|
||||
"environment": "隔离PG;实际S01/B04/B07链路,不证明文学效果",
|
||||
"given": "同一方法版本的五条真实挑选反馈(四选一拒,其一有原话)",
|
||||
"when": "汇总建议、采纳为推断偏好、审阅确认并消费",
|
||||
"then": [
|
||||
"建议带真实原话依据;采纳后pending,确认后active可消费"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/集成/test_推断偏好.py",
|
||||
"symbol": "test_建议采纳确认链__a10021",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_推断偏好.py::test_建议采纳确认链__a10021"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"参考环境",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"生成环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10022",
|
||||
"environment": "隔离PG;实际S01/B04/B07链路,不证明文学效果",
|
||||
"given": "支撑事件不足门槛的挑选台账",
|
||||
"when": "汇总建议并读取复利度量",
|
||||
"then": [
|
||||
"不足5事件不生成建议;度量比率的空值与已知值分列"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
"file": "tests/集成/test_推断偏好.py",
|
||||
"symbol": "test_门槛与度量空值语义__a10022",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_推断偏好.py::test_门槛与度量空值语义__a10022"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"参考环境",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"生成环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-work-current-binding",
|
||||
"environment": "隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程",
|
||||
|
||||
112
tests/集成/test_推断偏好.py
Normal file
112
tests/集成/test_推断偏好.py
Normal file
@ -0,0 +1,112 @@
|
||||
"""推断偏好建议的真实链路:台账聚合→建议→采纳为pending→确认active→可消费;复利度量空值语义。"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
import test_参考改写 as 改写测试
|
||||
|
||||
from muse.作者经验.接口 import (
|
||||
偏好决定,
|
||||
偏好范围,
|
||||
偏好请求,
|
||||
参考挑选反馈请求,
|
||||
挑选候选,
|
||||
读取偏好材料,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.数据库
|
||||
生成环境 = 改写测试.生成环境
|
||||
参考环境 = 改写测试.参考环境
|
||||
|
||||
|
||||
def _挑选请求(版本, selected, rejected, quote=""):
|
||||
return 参考挑选反馈请求(
|
||||
query="机甲近身刀法",
|
||||
domain="combat",
|
||||
candidates=(挑选候选(版本, 1, 0.03),),
|
||||
selected=selected,
|
||||
rejected=rejected,
|
||||
quote=quote,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10021",
|
||||
environment="隔离PG;实际S01/B04/B07链路,不证明文学效果",
|
||||
given="同一方法版本的五条真实挑选反馈(四选一拒,其一有原话)",
|
||||
when="汇总建议、采纳为推断偏好、审阅确认并消费",
|
||||
then=["建议带真实原话依据;采纳后pending,确认后active可消费"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_建议采纳确认链__a10021(参考环境):
|
||||
env = 参考环境
|
||||
经验 = env["装配"].要求作者经验()
|
||||
作者 = env["作者"]
|
||||
版本 = env["参考版本"]
|
||||
经验.记录参考挑选反馈(作者, "infer-1", _挑选请求(版本, (版本,), (), "这类受击反馈多来点"))
|
||||
for 序 in (2, 3, 4):
|
||||
经验.记录参考挑选反馈(作者, f"infer-{序}", _挑选请求(版本, (版本,), ()))
|
||||
经验.记录参考挑选反馈(作者, "infer-5", _挑选请求(版本, (), (版本,)))
|
||||
|
||||
建议 = 经验.汇总推断偏好建议(作者)
|
||||
assert len(建议) == 1
|
||||
条 = 建议[0]
|
||||
assert 条["quote"] == "这类受击反馈多来点"
|
||||
依据 = 经验.读取反馈(作者, 条["feedback_id"])
|
||||
assert 依据["payload"]["quote"] == 条["quote"]
|
||||
assert 依据["payload"]["种类"] == "参考挑选"
|
||||
assert 条["证据"]["选用次数"] == 4 and 条["证据"]["拒绝次数"] == 1
|
||||
|
||||
saved = 经验.保存偏好(
|
||||
作者,
|
||||
str(uuid4()),
|
||||
偏好请求(
|
||||
preference_id=str(uuid4()),
|
||||
topic=条["topic"],
|
||||
text=条["text"],
|
||||
quote=条["quote"],
|
||||
scope=偏好范围(),
|
||||
origin="inferred",
|
||||
feedback_id=条["feedback_id"],
|
||||
),
|
||||
)["results"][0]
|
||||
assert saved["state"] == "pending"
|
||||
|
||||
定位 = 偏好决定(saved["preference_id"], saved["revision"], saved["content_hash"])
|
||||
审阅 = 经验.打开偏好审阅(作者, 定位)
|
||||
confirmed = 经验.确认偏好(
|
||||
作者, str(uuid4()), 定位, 审阅ID=审阅["review_id"], 审阅哈希=审阅["review_hash"]
|
||||
)["results"][0]
|
||||
assert confirmed["state"] == "active"
|
||||
with env["库"].连接(只读=True) as 连:
|
||||
材料 = 读取偏好材料(连, 作者.作者, "gen-work")
|
||||
assert any(条["text"] == 项["text"] for 项 in 材料)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10022",
|
||||
environment="隔离PG;实际S01/B04/B07链路,不证明文学效果",
|
||||
given="支撑事件不足门槛的挑选台账",
|
||||
when="汇总建议并读取复利度量",
|
||||
then=["不足5事件不生成建议;度量比率的空值与已知值分列"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系",
|
||||
)
|
||||
def test_门槛与度量空值语义__a10022(参考环境):
|
||||
env = 参考环境
|
||||
经验 = env["装配"].要求作者经验()
|
||||
作者 = env["作者"]
|
||||
版本 = env["参考版本"]
|
||||
经验.记录参考挑选反馈(作者, "few-1", _挑选请求(版本, (版本,), ()))
|
||||
经验.记录参考挑选反馈(作者, "few-2", _挑选请求(版本, (), (版本,)))
|
||||
|
||||
assert 经验.汇总推断偏好建议(作者) == []
|
||||
度量 = 经验.读取复利度量(作者)
|
||||
assert 度量["改写对照"]["总数"] == 0
|
||||
assert 度量["改写对照"]["首稿采纳率"] is None # 分母为零保持空值,不混算零
|
||||
assert 度量["参考挑选"]["选用次数"] == 1
|
||||
assert 度量["参考挑选"]["拒绝次数"] == 1
|
||||
assert 度量["参考挑选"]["点踩率"] == 0.5
|
||||
assert 度量["causality"] == "not_established"
|
||||
|
||||
# 领域过滤:非 combat 领域无建议
|
||||
assert 经验.汇总推断偏好建议(作者, 领域="emotion") == []
|
||||
@ -9,14 +9,23 @@ import {
|
||||
打开审阅,
|
||||
确认偏好,
|
||||
撤回偏好,
|
||||
列出推断建议,
|
||||
读取复利度量,
|
||||
采纳建议,
|
||||
type 偏好行,
|
||||
type 偏好审阅,
|
||||
type 偏好定位,
|
||||
type 建议行,
|
||||
} from "./数据";
|
||||
|
||||
export function Ui偏好页() {
|
||||
const cache = useQueryClient();
|
||||
const rows = useQuery({ queryKey: ["作者偏好"], queryFn: 列出偏好 });
|
||||
const suggestions = useQuery({
|
||||
queryKey: ["推断偏好建议"],
|
||||
queryFn: () => 列出推断建议(),
|
||||
});
|
||||
const metrics = useQuery({ queryKey: ["复利度量"], queryFn: 读取复利度量 });
|
||||
const works = useQuery({
|
||||
queryKey: ["作品列表"],
|
||||
queryFn: ({ signal }) => 列出作品(signal),
|
||||
@ -64,6 +73,22 @@ export function Ui偏好页() {
|
||||
String(
|
||||
works.data?.find((w) => w.work_id === id)?.content.名称 ?? "指定作品",
|
||||
);
|
||||
async function 采纳(建议: 建议行) {
|
||||
setBusy(true);
|
||||
setMessage("");
|
||||
try {
|
||||
await 采纳建议(建议);
|
||||
setMessage("已保存为待确认偏好;在下方列表中审阅后才会生效。");
|
||||
await cache.invalidateQueries({ queryKey: ["作者偏好"] });
|
||||
await cache.invalidateQueries({ queryKey: ["推断偏好建议"] });
|
||||
} catch (e) {
|
||||
setMessage((e as Error).message);
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}
|
||||
const 百分比 = (值: number | null) =>
|
||||
值 === null ? "暂无数据" : `${Math.round(值 * 100)}%`;
|
||||
return (
|
||||
<main className="content">
|
||||
<p className="eyebrow">作者经验</p>
|
||||
@ -128,6 +153,46 @@ export function Ui偏好页() {
|
||||
</form>
|
||||
<p role="status">{message}</p>
|
||||
</section>
|
||||
<section className="paper-panel" aria-label="复利度量">
|
||||
<h2>复利度量</h2>
|
||||
{metrics.data ? (
|
||||
<>
|
||||
<p>
|
||||
首稿采纳率 {百分比(metrics.data.改写对照.首稿采纳率)}(改写对照{" "}
|
||||
{metrics.data.改写对照.总数} 条) · 参考挑选点踩率{" "}
|
||||
{百分比(metrics.data.参考挑选.点踩率)}(选用{" "}
|
||||
{metrics.data.参考挑选.选用次数} 次 / 拒绝{" "}
|
||||
{metrics.data.参考挑选.拒绝次数} 次)
|
||||
</p>
|
||||
<p className="muted">{metrics.data.说明}</p>
|
||||
</>
|
||||
) : (
|
||||
<p>正在读取度量…</p>
|
||||
)}
|
||||
</section>
|
||||
<section className="paper-panel" aria-label="推断偏好建议">
|
||||
<h2>推断建议</h2>
|
||||
<p className="muted">
|
||||
来自参考挑选台账的确定性统计(单版本 ≥5 次支撑事件才提出)。
|
||||
采纳只保存为待确认偏好,经你确认后才会生效。
|
||||
</p>
|
||||
{suggestions.data?.length === 0 && <p>暂无达到门槛的建议。</p>}
|
||||
{suggestions.data?.map((建议) => (
|
||||
<article key={建议.method_version_id + 建议.topic + 建议.text}>
|
||||
<h3>{建议.证据.方法标题}</h3>
|
||||
<p>{建议.text}</p>
|
||||
<p className="muted">
|
||||
依据:选用 {建议.证据.选用次数} 次 · 拒绝 {建议.证据.拒绝次数}{" "}
|
||||
次 · 支撑事件 {建议.支撑事件}
|
||||
</p>
|
||||
<blockquote>{建议.quote}</blockquote>
|
||||
<button disabled={busy} onClick={() => void 采纳(建议)}>
|
||||
采纳为待确认偏好
|
||||
</button>
|
||||
</article>
|
||||
))}
|
||||
{suggestions.error && <p role="alert">{suggestions.error.message}</p>}
|
||||
</section>
|
||||
{rows.error && <p role="alert">{rows.error.message}</p>}
|
||||
{rows.isPending && <p>正在读取偏好…</p>}
|
||||
{rows.data?.length === 0 && <p>还没有保存偏好。</p>}
|
||||
|
||||
@ -285,3 +285,66 @@ export async function 读取观察(improvement_id: string) {
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export interface 建议行 {
|
||||
topic: string;
|
||||
text: string;
|
||||
quote: string;
|
||||
feedback_id: string;
|
||||
method_version_id: string;
|
||||
domain: string;
|
||||
支撑事件: number;
|
||||
证据: {
|
||||
选用次数: number;
|
||||
拒绝次数: number;
|
||||
方法标题: string;
|
||||
measurement_source: string;
|
||||
causality: string;
|
||||
};
|
||||
}
|
||||
export interface 复利度量 {
|
||||
改写对照: {
|
||||
总数: number;
|
||||
首稿采纳率: number | null;
|
||||
[决定: string]: number | null;
|
||||
};
|
||||
参考挑选: {
|
||||
选用次数: number;
|
||||
拒绝次数: number;
|
||||
点踩率: number | null;
|
||||
有信号版本数: number;
|
||||
};
|
||||
measurement_source: string;
|
||||
causality: string;
|
||||
说明: string;
|
||||
}
|
||||
export async function 列出推断建议(domain = "") {
|
||||
return 取<建议行[]>(
|
||||
await 客户端.GET("/api/v1/inferred-preference-suggestions", {
|
||||
params: { query: { domain } },
|
||||
}),
|
||||
);
|
||||
}
|
||||
export async function 读取复利度量() {
|
||||
return 取<复利度量>(await 客户端.GET("/api/v1/compounding-metrics"));
|
||||
}
|
||||
export async function 采纳建议(建议: 建议行, workId = "") {
|
||||
return 取<{ results: 偏好行[] }>(
|
||||
await 客户端.POST("/api/v1/preferences", {
|
||||
body: {
|
||||
command_id: crypto.randomUUID(),
|
||||
preference: {
|
||||
preference_id: crypto.randomUUID(),
|
||||
topic: 建议.topic,
|
||||
text: 建议.text,
|
||||
quote: 建议.quote,
|
||||
scope: { kind: workId ? "work" : "author", work_id: workId, task_id: "" },
|
||||
origin: "inferred",
|
||||
duration: "persistent",
|
||||
feedback_id: 建议.feedback_id,
|
||||
expected_revision: 0,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -2051,6 +2051,40 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/inferred-preference-suggestions": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** 读推断建议 */
|
||||
get: operations["read_inferred_preference_suggestions"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/compounding-metrics": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** 读复利度量 */
|
||||
get: operations["read_compounding_metrics"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/api/v1/improvements": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -12630,6 +12664,61 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
read_inferred_preference_suggestions: {
|
||||
parameters: {
|
||||
query?: {
|
||||
domain?: string;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
[key: string]: unknown;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
/** @description Validation Error */
|
||||
422: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["HTTPValidationError"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
read_compounding_metrics: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
propose_improvement: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@ -16,6 +16,9 @@ const api = vi.hoisted(() => ({
|
||||
review: vi.fn(),
|
||||
confirm: vi.fn(),
|
||||
revoke: vi.fn(),
|
||||
suggest: vi.fn(),
|
||||
metrics: vi.fn(),
|
||||
adopt: vi.fn(),
|
||||
}));
|
||||
vi.mock("../../src/功能/作者经验/数据", () => ({
|
||||
列出偏好: api.list,
|
||||
@ -23,6 +26,9 @@ vi.mock("../../src/功能/作者经验/数据", () => ({
|
||||
打开审阅: api.review,
|
||||
确认偏好: api.confirm,
|
||||
撤回偏好: api.revoke,
|
||||
列出推断建议: () => api.suggest(),
|
||||
读取复利度量: () => api.metrics(),
|
||||
采纳建议: api.adopt,
|
||||
}));
|
||||
vi.mock("../../src/功能/作品规划/数据", () => ({ 列出作品: async () => [] }));
|
||||
function row(id = "pending", state = "pending", text = "用动作显现心理。") {
|
||||
@ -67,6 +73,15 @@ beforeEach(() => {
|
||||
api.confirm.mockResolvedValue({});
|
||||
api.revoke.mockResolvedValue({});
|
||||
api.save.mockResolvedValue({ results: [{ state: "active" }] });
|
||||
api.suggest.mockResolvedValue([]);
|
||||
api.metrics.mockResolvedValue({
|
||||
改写对照: { 总数: 0, 首稿采纳率: null },
|
||||
参考挑选: { 选用次数: 0, 拒绝次数: 0, 点踩率: null, 有信号版本数: 0 },
|
||||
measurement_source: "feedback_ledger",
|
||||
causality: "not_established",
|
||||
说明: "",
|
||||
});
|
||||
api.adopt.mockResolvedValue({ results: [{ state: "pending" }] });
|
||||
});
|
||||
afterEach(cleanup);
|
||||
|
||||
@ -140,3 +155,37 @@ it("NC-ui-preference-scope:任务要求不被编辑成长期范围", async ()
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// @muse-case {"case_id":"NC-ui-preference-infer","environment":"离线界面和传输替身","given":"确定性聚合产出的推断偏好建议","when":"作者点击采纳","then":["以建议原话与真实反馈保存为待确认偏好,不自动生效"],"contract":"docs/系统架构/新版设计/模块设计/B07-作者经验.md#偏好双轨关系"}
|
||||
it("NC-ui-preference-infer:推断建议采纳为待确认偏好", async () => {
|
||||
api.list.mockResolvedValue([]);
|
||||
api.suggest.mockResolvedValue([
|
||||
{
|
||||
topic: "参考偏好:combat",
|
||||
text: "「打戏」领域改写优先参考「机甲近身刀法」类技法。",
|
||||
quote: "这类受击反馈多来点",
|
||||
feedback_id: "fb-1",
|
||||
method_version_id: "v1",
|
||||
domain: "combat",
|
||||
支撑事件: 6,
|
||||
证据: {
|
||||
选用次数: 5,
|
||||
拒绝次数: 1,
|
||||
方法标题: "机甲近身刀法",
|
||||
measurement_source: "feedback_ledger",
|
||||
causality: "not_established",
|
||||
},
|
||||
},
|
||||
]);
|
||||
mount();
|
||||
const button = await screen.findByRole("button", {
|
||||
name: "采纳为待确认偏好",
|
||||
});
|
||||
fireEvent.click(button);
|
||||
await waitFor(() =>
|
||||
expect(api.adopt).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ feedback_id: "fb-1", topic: "参考偏好:combat" }),
|
||||
),
|
||||
);
|
||||
await screen.findByText("已保存为待确认偏好;在下方列表中审阅后才会生效。");
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user