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

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] did = name_map[nm][0]
to_update.setdefault(did, []).append(it["观察点"]) to_update.setdefault(did, []).append(it["观察点"])
pres_add.setdefault(did, set()).update(_int_chaps(it.get("出场章"))) 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) # ④ 卡更新(分批≤6)
items = sorted(to_update.items()) items = sorted(to_update.items())
for i in range(0, len(items), UPDATE_BATCH): 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( relation_touched.add(
_insert_relation_card(conn, work_id, win_no, rp) _insert_relation_card(conn, work_id, win_no, rp)
) )
# ⑥ 机械收尾:出场章统一并入(纯出场 + 被更新卡,抽检#2)+ 窗置 done # ⑥ 机械收尾:仅处理未被更新模型消费的出场章,然后窗置 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("出场章"))
)
presence_touched = set() presence_touched = set()
for did, chs in pres_add.items(): for did, chs in pres_add.items():
if not chs: if not chs:

View File

@ -2438,10 +2438,12 @@ def test_run_pure_presence_rejects_external_state_and_revision_conflicts():
def test_run_presence_paths_accept_numeric_strings(): def test_run_presence_paths_accept_numeric_strings():
"""run 的已知更新归并卡与纯出场都把数字字符串章号落成整数。""" """同卡横跨新信息与纯出场时,有无模型更新都只走一次实体写入并落全章节。"""
def run_case(updates):
state = { state = {
"window_status": "pending", "window_status": "pending",
"revisions": {701: 0},
"payload": { "payload": {
"type": "character", "type": "character",
"名称": "安若雪", "名称": "安若雪",
@ -2461,7 +2463,7 @@ def test_run_presence_paths_accept_numeric_strings():
], ],
"纯出场": [{"名称": "安若雪", "出场章": ["71"]}], "纯出场": [{"名称": "安若雪", "出场章": ["71"]}],
}, {}) }, {})
return ({"更新": []}, {}) return ({"更新": deepcopy(updates)}, {})
with patch.object(pu.psycopg, "connect", side_effect=lambda *_: _RunConn(state)), \ 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_entity_contracts", return_value=_fake_contracts()), \
@ -2475,16 +2477,32 @@ def test_run_presence_paths_accept_numeric_strings():
"load_known", "load_known",
return_value=({"安若雪": (701, "character", "旧摘要")}, {}, {}, {701: 0}), return_value=({"安若雪": (701, "character", "旧摘要")}, {}, {}, {701: 0}),
), \ ), \
patch.object(pu, "m3_json", side_effect=fake_m3_json): patch.object(pu, "m3_json", side_effect=fake_m3_json), \
pu._run( patch.object(pu, "undo_window"), \
work_id=8, patch.object(pu, "merge_card", wraps=pu.merge_card) as merge_mock, \
max_windows=0, patch.object(
max_calls=0, pu, "_write_locked_upgrade_draft", wraps=pu._write_locked_upgrade_draft
model="MiniMax-M3", ) as write_mock:
redo_window=0, pu._run(8, 0, 0, "MiniMax-M3", 0, False)
semantic_on=False, return state, merge_mock.call_count, write_mock.call_count
)
check("run-presence-归并与纯出场数字字符串均落库", state["payload"]["出场章"] == [70, 71]) 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(): def test_load_known_projection():