修复: 同窗统一消费实体出场章

This commit is contained in:
zizi 2026-07-22 16:44:25 +08:00
parent d015f1d321
commit 7f675922b2
2 changed files with 70 additions and 51 deletions

View File

@ -2398,6 +2398,13 @@ def _run(work_id, max_windows, max_calls, model, redo_window, semantic_on):
did = name_map[nm][0]
to_update.setdefault(did, []).append(it["观察点"])
pres_add.setdefault(did, set()).update(_int_chaps(it.get("出场章")))
# 在任何实体更新写入前汇总纯出场,确保同卡全部章节由一次 merge 或机械收尾消费。
for it in obs.get("纯出场", []):
nm = (it.get("名称") or "").strip()
if nm in name_map:
pres_add.setdefault(name_map[nm][0], set()).update(
_int_chaps(it.get("出场章"))
)
# ④ 卡更新(分批≤6)
items = sorted(to_update.items())
for i in range(0, len(items), UPDATE_BATCH):
@ -2501,13 +2508,7 @@ def _run(work_id, max_windows, max_calls, model, redo_window, semantic_on):
relation_touched.add(
_insert_relation_card(conn, work_id, win_no, rp)
)
# ⑥ 机械收尾:出场章统一并入(纯出场 + 被更新卡,抽检#2)+ 窗置 done
for it in obs.get("纯出场", []):
nm = (it.get("名称") or "").strip()
if nm in name_map:
pres_add.setdefault(name_map[nm][0], set()).update(
_int_chaps(it.get("出场章"))
)
# ⑥ 机械收尾:仅处理未被更新模型消费的出场章,然后窗置 done。
presence_touched = set()
for did, chs in pres_add.items():
if not chs:

View File

@ -2438,53 +2438,71 @@ def test_run_pure_presence_rejects_external_state_and_revision_conflicts():
def test_run_presence_paths_accept_numeric_strings():
"""run 的已知更新归并卡与纯出场都把数字字符串章号落成整数。"""
"""同卡横跨新信息与纯出场时,有无模型更新都只走一次实体写入并落全章节。"""
state = {
"window_status": "pending",
"payload": {
"type": "character",
"名称": "安若雪",
"别名": [],
"字段": {},
"出场章": [],
"_work_id": 8,
},
}
def run_case(updates):
state = {
"window_status": "pending",
"revisions": {701: 0},
"payload": {
"type": "character",
"名称": "安若雪",
"别名": [],
"字段": {},
"出场章": [],
"_work_id": 8,
},
}
def fake_m3_json(prompt, model, need_keys, system=None):
if need_keys == ("新名字", "已知实体新信息", "纯出场"):
return ({
"新名字": [],
"已知实体新信息": [
{"名称": "安若雪", "观察点": "出现新变化", "出场章": ["70"]}
],
"纯出场": [{"名称": "安若雪", "出场章": ["71"]}],
}, {})
return ({"更新": []}, {})
def fake_m3_json(prompt, model, need_keys, system=None):
if need_keys == ("新名字", "已知实体新信息", "纯出场"):
return ({
"新名字": [],
"已知实体新信息": [
{"名称": "安若雪", "观察点": "出现新变化", "出场章": ["70"]}
],
"纯出场": [{"名称": "安若雪", "出场章": ["71"]}],
}, {})
return ({"更新": deepcopy(updates)}, {})
with patch.object(pu.psycopg, "connect", side_effect=lambda *_: _RunConn(state)), \
patch.object(pu, "load_entity_contracts", return_value=_fake_contracts()), \
patch.object(
pu,
"load_window_material",
return_value=("安若雪连续出现", {70: "安若雪出现新变化。", 71: "安若雪继续前进。"}),
), \
patch.object(
pu,
"load_known",
return_value=({"安若雪": (701, "character", "旧摘要")}, {}, {}, {701: 0}),
), \
patch.object(pu, "m3_json", side_effect=fake_m3_json):
pu._run(
work_id=8,
max_windows=0,
max_calls=0,
model="MiniMax-M3",
redo_window=0,
semantic_on=False,
)
check("run-presence-归并与纯出场数字字符串均落库", state["payload"]["出场章"] == [70, 71])
with patch.object(pu.psycopg, "connect", side_effect=lambda *_: _RunConn(state)), \
patch.object(pu, "load_entity_contracts", return_value=_fake_contracts()), \
patch.object(
pu,
"load_window_material",
return_value=("安若雪连续出现", {70: "安若雪出现新变化。", 71: "安若雪继续前进。"}),
), \
patch.object(
pu,
"load_known",
return_value=({"安若雪": (701, "character", "旧摘要")}, {}, {}, {701: 0}),
), \
patch.object(pu, "m3_json", side_effect=fake_m3_json), \
patch.object(pu, "undo_window"), \
patch.object(pu, "merge_card", wraps=pu.merge_card) as merge_mock, \
patch.object(
pu, "_write_locked_upgrade_draft", wraps=pu._write_locked_upgrade_draft
) as write_mock:
pu._run(8, 0, 0, "MiniMax-M3", 0, False)
return state, merge_mock.call_count, write_mock.call_count
updated, merge_calls, write_calls = run_case([
{"draft_id": 701, "变更字段": {"一句话摘要": "新摘要"}, "别名新增": []},
])
check("run-presence-有更新时只写一次",
merge_calls == 1 and write_calls == 1 and updated["revisions"][701] == 1,
detail=f"merge={merge_calls}, write={write_calls}, state={updated}")
check("run-presence-有更新时一次消费两数组章节",
updated["window_status"] == "done" and updated["payload"]["出场章"] == [70, 71],
detail=str(updated))
unchanged, merge_calls, write_calls = run_case([])
check("run-presence-无更新时由机械收尾写一次",
merge_calls == 0 and write_calls == 1 and unchanged["revisions"][701] == 1,
detail=f"merge={merge_calls}, write={write_calls}, state={unchanged}")
check("run-presence-无更新时两数组章节仍落库",
unchanged["window_status"] == "done" and unchanged["payload"]["出场章"] == [70, 71],
detail=str(unchanged))
def test_load_known_projection():