框架: 升格管线 7 处守卫落地(fable5 抽检 5 高危根治)

- 结构垃圾拦截:变更包字符串化文本拒收留审计(H2 窗75事故,追加+覆写双路)
- 摘要特判写卡顶层+纳入更新流程(M4 影子摘要/600章旧摘要)
- 追加条目按窗号稳定排序归位(M2 补跑窗时间线倒流)
- 立卡初始别名同步入 alias 表(L3 覆盖率26%致判重空转)
- 同型名称子串防漏并:转观察材料留复核标记(H5 果子/开心果子)
- 别名拒单字(M5)
- prompt 纪律:禁相对时间指代/只写本实体信息/摘要跟进(H1 M3)
This commit is contained in:
zizi 2026-07-15 21:49:54 +08:00
parent 0ac8f7ce04
commit 0892bcad7c

View File

@ -22,6 +22,7 @@
"""
import json
import pathlib
import re
import sys
import click
@ -194,8 +195,9 @@ def update_prompt(contracts, title, a, b, text, cards_with_obs):
下列每张卡给出当前卡全文本窗观察点对照本窗正文**只输出需要变更的字段**
- 覆写类字段性格底色/说话方式/当前状态等标量**必须输出该字段完整的新全量值**旧值里仍然成立的信息要保留进新值禁止只写"新增…"式增量那会把旧信息抹掉
- 追加类字段成长弧线/演变轨迹等数组只输出本窗新增条目不要重抄旧条目不要自己加 [窗N] 前缀系统会加
- 没有变化的字段不要输出整卡无实质变化则不输出该卡
纪律以正文为证据不脑补数值必须正文原样出现禁止推算字段 key 必须来自该型合同每卡别名有新发现可在别名新增里给只收**该实体自己**的新别名他人对它的称呼算它对别的实体的称呼不算且必须是可在正文原样出现的纯名字禁带括号注释与说明文字
- 没有变化的字段不要输出整卡无实质变化则不输出该卡
- 该实体的身份/处境已发生重大变化时一句话摘要也作为变更字段输出写当前态全量
纪律以正文为证据不脑补数值必须正文原样出现禁止推算字段 key 必须来自该型合同**禁止本窗/本段/近期等相对时间指代**写绝对状态或带章号卡会脱离本窗被单独阅读只写**该实体自己**的信息他人的任命/心理/变化不得写入本卡每卡别名有新发现可在别名新增里给只收**该实体自己**的新别名他人对它的称呼算它对别的实体的称呼不算且必须是可在正文原样出现的纯名字禁带括号注释与说明文字禁单字
相关型字段合同
{render_entity_contracts(contracts, sorted({p.get("type") for _, p, _ in cards_with_obs} & set(ENTITY_TYPES)))}
@ -255,15 +257,30 @@ def _strip_prefix(s):
def _clean_alias(al):
"""别名机械准入(抽检#6拒收含括号注释/超长的备忘录式别名——预扫精确匹配永不命中=死数据。"""
"""别名机械准入(抽检#6拒收含括号注释/超长的备忘录式别名——预扫精确匹配永不命中=死数据。
单字别名一并拒收复检 M5''字入表后预扫全文命中率爆炸纯噪声"""
al = (al or "").strip()
if not al or len(al) > 12:
if not al or len(al) < 2 or len(al) > 12:
return None
if any(c in al for c in "()。,,"):
return None
return al
def _is_garbage(text):
"""结构垃圾检测(抽检 H2 根治窗75 实测模型把原始变更包字符串化塞进字段值,
7 张卡被 {'draft_id':...} 类程序结构污染含结构特征的文本一律拒收留审计"""
t = str(text)
return ("draft_id" in t or "变更字段" in t or "别名新增" in t
or t.lstrip().startswith(("{'", '{"', "[{")))
def _entry_win(x):
"""追加条目的窗号(无 [窗N] 前缀的初卡条目记 0排最前"""
m = re.match(r"^\[窗(\d+)\]", str(x))
return int(m.group(1)) if m else 0
def merge_card(conn, draft_id, win_no, changes, alias_new, valid_keys=None):
"""按 5.1 三类规则合并变更字段:数组/白名单=追加(剥模型自带前缀+同文去重后带窗号),
标量=覆写留审计增量式假全量拦截转追加抽检#4 信息回退病)。
@ -292,6 +309,27 @@ def merge_card(conn, draft_id, win_no, changes, alias_new, valid_keys=None):
(draft_id, win_no, ("越合同:" + str(k))[:100],
json.dumps(v, ensure_ascii=False)[:2000], TENANT))
continue
if k == "一句话摘要":
# 摘要住卡顶层而非"字段"子对象——此前写进子对象成"影子摘要"且顶层摘要
# 从不更新(抽检 M4600 章前的旧摘要一直挂着)。特判写顶层,同守水位闸。
if isinstance(v, str) and v.strip() and not _is_garbage(v):
if win_no < watermark:
conn.execute(
"""INSERT INTO example_upgrade_audit
(draft_id, window_no, field_name, old_value, new_value, tenant_id)
VALUES (%s,%s,%s,NULL,%s,%s)""",
(draft_id, win_no, "迟到覆写弃用:一句话摘要",
json.dumps(v, ensure_ascii=False)[:2000], TENANT))
else:
conn.execute(
"""INSERT INTO example_upgrade_audit
(draft_id, window_no, field_name, old_value, new_value, tenant_id)
VALUES (%s,%s,%s,%s,%s,%s)""",
(draft_id, win_no, "一句话摘要",
json.dumps(payload.get("一句话摘要"), ensure_ascii=False),
json.dumps(v.strip(), ensure_ascii=False), TENANT))
payload["一句话摘要"] = v.strip()
continue
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 \
@ -303,10 +341,27 @@ def merge_card(conn, draft_id, win_no, changes, alias_new, valid_keys=None):
seen = {_strip_prefix(x) for x in old} # 同文去重(抽检#1 重复病)
for it in items:
core = _strip_prefix(it) # 剥模型自带前缀(抽检#1 堆叠病)
if core and _is_garbage(core):
# 结构垃圾条目拒收留审计窗75 事故根治:变更包字符串化混进字段值)
conn.execute(
"""INSERT INTO example_upgrade_audit
(draft_id, window_no, field_name, old_value, new_value, tenant_id)
VALUES (%s,%s,%s,NULL,%s,%s)""",
(draft_id, win_no, ("垃圾拦截:" + str(k))[:100],
str(core)[:2000], TENANT))
continue
if core and core not in seen:
old.append(f"[窗{win_no}] {core}")
seen.add(core)
fields[k] = old
# 窗序归位(抽检 M2补跑/重试窗条目尾插致时间线倒流)——稳定排序,同窗保持原序
fields[k] = sorted(old, key=_entry_win)
elif isinstance(v, str) and _is_garbage(v):
# 结构垃圾覆写值拒收留审计同窗75 事故根治)
conn.execute(
"""INSERT INTO example_upgrade_audit
(draft_id, window_no, field_name, old_value, new_value, tenant_id)
VALUES (%s,%s,%s,NULL,%s,%s)""",
(draft_id, win_no, ("垃圾拦截:" + str(k))[:100], v[:2000], TENANT))
elif win_no < watermark:
# 迟到覆写弃用:本窗时序早于卡已生长到的窗位,覆写会让卡态倒流——
# 只留审计(标记可查),卡体保持高窗态不动
@ -386,6 +441,15 @@ def new_card(conn, work_id, win_no, ent):
(draft_id, window_no, field_name, old_value, new_value, tenant_id)
VALUES (%s,%s,%s,NULL,%s,%s)""",
(did, win_no, k, json.dumps(v, ensure_ascii=False), TENANT))
# 初始别名同步入 alias 表(抽检 L3 根源:立卡别名只写卡内不入表,
# 别名表覆盖率仅 26%"别名精确判重"层大面积空转导致同人两卡漏并)
for al in payload["别名"]:
conn.execute(
"""INSERT INTO example_upgrade_alias
(work_id, canonical_name, alias, evidence_window, verdict_by, tenant_id)
VALUES (%s,%s,%s,%s,'init',%s)
ON CONFLICT (tenant_id, work_id, alias) DO NOTHING""",
(work_id, raw, al, win_no, TENANT))
conn.execute(
"""INSERT INTO example_upgrade_card_state (draft_id, work_id, watermark_window, tenant_id)
VALUES (%s,%s,%s,%s) ON CONFLICT (draft_id) DO NOTHING""",
@ -542,6 +606,24 @@ def run(work_id, max_windows, max_calls, model, redo_window):
ON CONFLICT (tenant_id, work_id, alias) DO NOTHING""",
(work_id, hint, nm, win_no, TENANT))
continue
# 同型名称互为子串(抽检 H5「果子」vs「开心果子」同人两卡
# 不直接立卡,转观察材料并入既有卡由更新步 AI 甄别(软防护,留复核标记)
sub_hit = next(
(ex for ex, (d0, t0, _) in name_map.items()
if t0 == ent.get("", "") and len(nm) >= 2 and len(ex) >= 2
and nm != ex and (nm in ex or ex in nm)), None)
if sub_hit:
did = name_map[sub_hit][0]
to_update.setdefault(did, []).append(
f"(名称疑似同一实体「{nm}」≈「{sub_hit}」,请甄别后再并入)"
f"初卡材料:{json.dumps(ent, ensure_ascii=False)[:400]}")
conn.execute(
"""INSERT INTO example_upgrade_alias
(work_id, canonical_name, alias, evidence_window, verdict_by, tenant_id)
VALUES (%s,%s,%s,%s,'substr',%s)
ON CONFLICT (tenant_id, work_id, alias) DO NOTHING""",
(work_id, sub_hit, nm, win_no, TENANT))
continue
chaps = set(ent.get("出场章", []))
hist = presence.get((ent.get("", ""), nm), set())
if len(chaps | hist) >= 2: # 跨章(含跨窗合计)→ 立卡