框架: 抽检4高2中修复(前缀去重/出场章并入更新卡/关系落点统一/覆写回退拦截/别名准入/立卡剥括号)+存量回洗
This commit is contained in:
parent
e697ee0233
commit
b5b269f97b
@ -168,7 +168,7 @@ def observe_prompt(contracts, title, a, b, text, onstage):
|
||||
1) 新名字:正文出现、但「在场已知实体」清单里没有的实体(六型:{"/".join(ENTITY_TYPES)})。每个给:型、名称、别名、一句话摘要、按该型合同能填的字段(有正文证据才填)、出场章号。若你怀疑它其实是清单中某已知实体的别名/改名/化名,填「疑似别名指向」。
|
||||
2) 已知实体新信息:清单中实体在本窗的实质新信息(境界变化/性格显露/重大经历/立场转变),一条 ≤60 字观察点。没有实质新信息的不要报。
|
||||
3) 纯出场:清单中实体本窗出现但无实质新信息的,只报名称+出场章。
|
||||
纪律:一次性龙套(单章无名或仅路过)不报进新名字;实体判据与字段以合同为准,无证据不填;不脑补。
|
||||
纪律:一次性龙套(单章无名或仅路过)不报进新名字;实体判据与字段以合同为准,无证据不填;不脑补。数值(战力/指数/排名等)必须正文原样出现才可写,禁止推算或编造。组织改组/合并产生的新组织是**新实体**(走新名字),不是旧组织的别名。「疑似别名指向」只在确为同一实体改名/化名时填。
|
||||
|
||||
【六型字段合同】
|
||||
{render_entity_contracts(contracts, ENTITY_TYPES)}
|
||||
@ -192,10 +192,10 @@ def update_prompt(contracts, title, a, b, text, cards_with_obs):
|
||||
for d, p, o in cards_with_obs], ensure_ascii=False, indent=1)
|
||||
return f"""【功能指令(parse-book 作品面升格·卡增量更新)】
|
||||
下列每张卡给出「当前卡全文」与「本窗观察点」。对照本窗正文,**只输出需要变更的字段**:
|
||||
- 覆写类字段(性格底色/说话方式/当前状态等标量):输出更准确的新值(整字段替换);
|
||||
- 追加类字段(成长弧线/演变轨迹等数组):只输出本窗新增条目(不要重抄旧条目);
|
||||
- 覆写类字段(性格底色/说话方式/当前状态等标量):**必须输出该字段完整的新全量值**——旧值里仍然成立的信息要保留进新值,禁止只写"新增…"式增量(那会把旧信息抹掉);
|
||||
- 追加类字段(成长弧线/演变轨迹等数组):只输出本窗新增条目(不要重抄旧条目,不要自己加 [窗N] 前缀,系统会加);
|
||||
- 没有变化的字段不要输出;整卡无实质变化则不输出该卡。
|
||||
纪律:以正文为证据,不脑补;字段 key 必须来自该型合同;每卡别名有新发现可在「别名新增」里给——只收**该实体自己**的新别名(他人对它的称呼算,它对别的实体的称呼不算:窗2实测「魔鬼金」是主角喊宠物机的诨名,错报成了主角别名)。
|
||||
纪律:以正文为证据,不脑补;数值必须正文原样出现,禁止推算;字段 key 必须来自该型合同;每卡别名有新发现可在「别名新增」里给——只收**该实体自己**的新别名(他人对它的称呼算,它对别的实体的称呼不算),且必须是可在正文原样出现的纯名字(禁带括号注释与说明文字)。
|
||||
|
||||
【相关型字段合同】
|
||||
{render_entity_contracts(contracts, sorted({p.get("type") for _, p, _ in cards_with_obs} & set(ENTITY_TYPES)))}
|
||||
@ -241,8 +241,31 @@ def relation_prompt(contracts, title, a, b, text, char_cards, existing_rels):
|
||||
|
||||
# ── 合并与审计(三类字段演进 + 撤销依据)──
|
||||
|
||||
WIN_PREFIX_RE = None # 延迟编译(模块顶部 import re 已有)
|
||||
|
||||
|
||||
def _strip_prefix(s):
|
||||
"""剥条目行首的 [窗N]/[本窗] 前缀(可能多重堆叠),返回纯内容。"""
|
||||
import re as _re
|
||||
global WIN_PREFIX_RE
|
||||
if WIN_PREFIX_RE is None:
|
||||
WIN_PREFIX_RE = _re.compile(r"^(?:\[窗\d+\]\s*|\[本窗\]\s*)+")
|
||||
return WIN_PREFIX_RE.sub("", str(s)).strip()
|
||||
|
||||
|
||||
def _clean_alias(al):
|
||||
"""别名机械准入(抽检#6):拒收含括号注释/超长的备忘录式别名——预扫精确匹配永不命中=死数据。"""
|
||||
al = (al or "").strip()
|
||||
if not al or len(al) > 12:
|
||||
return None
|
||||
if any(c in al for c in "()()。,,:"):
|
||||
return None
|
||||
return al
|
||||
|
||||
|
||||
def merge_card(conn, draft_id, win_no, changes, alias_new):
|
||||
"""按 5.1 三类规则合并变更字段:数组/白名单=追加(条目带窗号),标量=覆写留审计。"""
|
||||
"""按 5.1 三类规则合并变更字段:数组/白名单=追加(剥模型自带前缀+同文去重后带窗号),
|
||||
标量=覆写留审计(增量式假全量拦截转追加——抽检#4 信息回退病)。"""
|
||||
payload = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
|
||||
(draft_id,)).fetchone()[0]
|
||||
fields = payload.setdefault("字段", {})
|
||||
@ -252,10 +275,20 @@ def merge_card(conn, draft_id, win_no, changes, alias_new):
|
||||
[a for a in (changes.pop("别名新增", None) or []) if isinstance(a, str)]
|
||||
for k, v in changes.items():
|
||||
is_append = k in APPEND_FIELDS or isinstance(v, list) or isinstance(fields.get(k), list)
|
||||
# 覆写值以增量口吻开头=模型把增量当全量(抽检#4:新值会抹掉旧基线),转追加不覆写
|
||||
if not is_append and isinstance(v, str) and \
|
||||
any(v.lstrip().startswith(w) for w in ("新增", "本窗", "另外", "此外")):
|
||||
is_append = True
|
||||
if is_append:
|
||||
items = v if isinstance(v, list) else [v]
|
||||
old = fields.get(k) if isinstance(fields.get(k), list) else ([fields[k]] if fields.get(k) else [])
|
||||
fields[k] = old + [f"[窗{win_no}] {it}" for it in items if it]
|
||||
seen = {_strip_prefix(x) for x in old} # 同文去重(抽检#1 重复病)
|
||||
for it in items:
|
||||
core = _strip_prefix(it) # 剥模型自带前缀(抽检#1 堆叠病)
|
||||
if core and core not in seen:
|
||||
old.append(f"[窗{win_no}] {core}")
|
||||
seen.add(core)
|
||||
fields[k] = old
|
||||
else:
|
||||
conn.execute(
|
||||
"""INSERT INTO example_upgrade_audit
|
||||
@ -265,17 +298,18 @@ def merge_card(conn, draft_id, win_no, changes, alias_new):
|
||||
if fields.get(k) is not None else None,
|
||||
json.dumps(v, ensure_ascii=False), TENANT))
|
||||
fields[k] = v
|
||||
for al in alias_new or []:
|
||||
if al and al.strip() and al.strip() != payload.get("名称"):
|
||||
for al0 in alias_new or []:
|
||||
al = _clean_alias(al0)
|
||||
if al and al != payload.get("名称"):
|
||||
payload.setdefault("别名", [])
|
||||
if al.strip() not in payload["别名"]:
|
||||
payload["别名"].append(al.strip())
|
||||
if al not in payload["别名"]:
|
||||
payload["别名"].append(al)
|
||||
conn.execute(
|
||||
"""INSERT INTO example_upgrade_alias
|
||||
(work_id, canonical_name, alias, evidence_window, verdict_by, tenant_id)
|
||||
VALUES (%s,%s,%s,%s,'ai',%s)
|
||||
ON CONFLICT (tenant_id, work_id, alias) DO NOTHING""",
|
||||
(payload.get("_work_id") or 0, payload.get("名称"), al.strip(), win_no, TENANT))
|
||||
(payload.get("_work_id") or 0, payload.get("名称"), al, win_no, TENANT))
|
||||
conn.execute(
|
||||
"""UPDATE muse_knowledge_draft SET draft_payload=%s, revision=revision+1,
|
||||
updater='upgrade' WHERE id=%s""",
|
||||
@ -289,9 +323,21 @@ def merge_card(conn, draft_id, win_no, changes, alias_new):
|
||||
|
||||
|
||||
def new_card(conn, work_id, win_no, ent):
|
||||
"""立初卡:payload 全字段以旧值=NULL 入审计(G7,错认拆回可还原初始态)。"""
|
||||
payload = {"type": ent["型"], "名称": ent["名称"].strip(),
|
||||
"别名": [a.strip() for a in ent.get("别名", []) if a and a.strip()],
|
||||
"""立初卡:payload 全字段以旧值=NULL 入审计(G7,错认拆回可还原初始态)。
|
||||
|
||||
名称剥括号注(抽检#7 根因):「白色游魂(无名侦察兵)」这类名称使预扫精确匹配失明
|
||||
——主名之外的括号内容若像名字则转别名,否则丢弃。"""
|
||||
import re as _re
|
||||
raw = ent["名称"].strip()
|
||||
m = _re.match(r"^(.+?)[((](.+?)[))]\s*$", raw)
|
||||
extra_alias = []
|
||||
if m:
|
||||
raw = m.group(1).strip()
|
||||
note = _clean_alias(m.group(2))
|
||||
if note:
|
||||
extra_alias.append(note)
|
||||
payload = {"type": ent["型"], "名称": raw,
|
||||
"别名": [x for x in ([_clean_alias(a) for a in ent.get("别名", [])] + extra_alias) if x],
|
||||
"一句话摘要": ent.get("一句话摘要", ""),
|
||||
"字段": ent.get("字段", {}) or {},
|
||||
"出场章": sorted(set(ent.get("出场章", []))),
|
||||
@ -477,10 +523,14 @@ def run(work_id, max_windows, max_calls, model, redo_window):
|
||||
VALUES (%s,%s,%s,%s,%s,%s,%s)""",
|
||||
(work_id, win_no, ch, ent.get("型", ""), nm,
|
||||
ent.get("一句话摘要", ""), TENANT))
|
||||
pres_add = {} # did -> set(出场章)——被更新的卡也要记出场章(抽检#2:只走纯出场路径整窗丢章)
|
||||
for it in obs.get("已知实体新信息", []):
|
||||
nm = (it.get("名称") or "").strip()
|
||||
if nm in name_map and it.get("观察点"):
|
||||
to_update.setdefault(name_map[nm][0], []).append(it["观察点"])
|
||||
did = name_map[nm][0]
|
||||
to_update.setdefault(did, []).append(it["观察点"])
|
||||
pres_add.setdefault(did, set()).update(
|
||||
c for c in (it.get("出场章") or []) if isinstance(c, int))
|
||||
# ④ 卡更新(分批≤6)
|
||||
items = sorted(to_update.items())
|
||||
for i in range(0, len(items), UPDATE_BATCH):
|
||||
@ -529,10 +579,28 @@ def run(work_id, max_windows, max_calls, model, redo_window):
|
||||
if ja not in id2name or yi not in id2name or ja == yi:
|
||||
continue
|
||||
key = tuple(sorted((ja, yi)))
|
||||
evo = f"[窗{win_no}] {r.get('本窗演变', '')}"
|
||||
evo_core = _strip_prefix(r.get("本窗演变", ""))
|
||||
if key in exist:
|
||||
# 落点统一(抽检#3):其他字段与演变轨迹全并进 rp["字段"],
|
||||
# 顶层遗留的演变轨迹迁移进字段后删除,避免双份分裂
|
||||
rid, rp = exist[key]
|
||||
rp.setdefault("演变轨迹", []).append(evo)
|
||||
f2 = rp.setdefault("字段", {})
|
||||
if "演变轨迹" in rp: # 存量顶层迁移
|
||||
legacy = rp.pop("演变轨迹")
|
||||
base = f2.get("演变轨迹") or []
|
||||
f2["演变轨迹"] = (base if isinstance(base, list) else [base]) + \
|
||||
(legacy if isinstance(legacy, list) else [legacy])
|
||||
for k2, v2 in (r.get("其他字段") or {}).items():
|
||||
if k2 == "演变轨迹":
|
||||
continue # 演变只走本窗演变通道
|
||||
f2[k2] = v2 # 覆写(当前状态等保最新)
|
||||
if evo_core:
|
||||
lst = f2.setdefault("演变轨迹", [])
|
||||
if not isinstance(lst, list):
|
||||
lst = [lst]
|
||||
f2["演变轨迹"] = lst
|
||||
if evo_core not in {_strip_prefix(x) for x in lst}:
|
||||
lst.append(f"[窗{win_no}] {evo_core}")
|
||||
if r.get("关系类型"):
|
||||
rp["关系类型"] = r["关系类型"]
|
||||
conn.execute(
|
||||
@ -540,12 +608,14 @@ def run(work_id, max_windows, max_calls, model, redo_window):
|
||||
revision=revision+1 WHERE id=%s""",
|
||||
(json.dumps(rp, ensure_ascii=False), rid))
|
||||
else:
|
||||
f2 = dict(r.get("其他字段") or {})
|
||||
f2["演变轨迹"] = [f"[窗{win_no}] {evo_core}"] if evo_core else []
|
||||
rp = {"type": RELATION_TYPE,
|
||||
"名称": f"{id2name[ja]}×{id2name[yi]}",
|
||||
"甲方draft": ja, "乙方draft": yi,
|
||||
"甲方名称": id2name[ja], "乙方名称": id2name[yi],
|
||||
"关系类型": r.get("关系类型", ""),
|
||||
"演变轨迹": [evo], "字段": r.get("其他字段", {}) or {},
|
||||
"字段": f2,
|
||||
"来源": f"升格@窗{win_no}", "状态": "草稿",
|
||||
"目标库": "本书作品库", "可见范围": "本书私有",
|
||||
"_work_id": work_id}
|
||||
@ -556,17 +626,20 @@ def run(work_id, max_windows, max_calls, model, redo_window):
|
||||
VALUES (%s,'entity',%s,'pending',%s,%s,'upgrade','upgrade',%s)""",
|
||||
(work_id, json.dumps(rp, ensure_ascii=False), SOURCE_TYPE,
|
||||
work_id, TENANT))
|
||||
# ⑥ 机械收尾:纯出场留档 + 窗置 done
|
||||
# ⑥ 机械收尾:出场章统一并入(纯出场 + 被更新卡,抽检#2)+ 窗置 done
|
||||
for it in obs.get("纯出场", []):
|
||||
nm = (it.get("名称") or "").strip()
|
||||
if nm in name_map:
|
||||
did = name_map[nm][0]
|
||||
p = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
|
||||
(did,)).fetchone()[0]
|
||||
chs = sorted(set(p.get("出场章", [])) | set(it.get("出场章", [])))
|
||||
p["出场章"] = chs
|
||||
conn.execute("UPDATE muse_knowledge_draft SET draft_payload=%s WHERE id=%s",
|
||||
(json.dumps(p, ensure_ascii=False), did))
|
||||
pres_add.setdefault(name_map[nm][0], set()).update(
|
||||
c for c in (it.get("出场章") or []) if isinstance(c, int))
|
||||
for did, chs in pres_add.items():
|
||||
if not chs:
|
||||
continue
|
||||
p = conn.execute("SELECT draft_payload FROM muse_knowledge_draft WHERE id=%s",
|
||||
(did,)).fetchone()[0]
|
||||
p["出场章"] = sorted(set(p.get("出场章", [])) | chs)
|
||||
conn.execute("UPDATE muse_knowledge_draft SET draft_payload=%s WHERE id=%s",
|
||||
(json.dumps(p, ensure_ascii=False), did))
|
||||
conn.execute(
|
||||
"""UPDATE example_upgrade_window SET status='done', error_message=NULL,
|
||||
updater='upgrade' WHERE tenant_id=%s AND work_id=%s AND window_no=%s""",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user