框架: 升格全量重抽准备脚本——卡软删+串行状态表清理(逐表核实约束定边界)

This commit is contained in:
zizi 2026-07-18 09:38:49 +08:00
parent 9f90f399ac
commit 26f5dad589

View File

@ -0,0 +1,105 @@
#!/usr/bin/env python3
"""parse-book skill:升格全量重抽准备——清一本书的升格产出与串行状态(2026-07-18 批9c 收尾)。
为什么需要它(锚点「全量重抽的管线坑」第 1 条):升格是**串行有状态**管线,直接重跑会被
旧状态卡死——窗全是 done 不再处理、预扫/判重底册装着旧代卡把新观察并进废卡、旧审计行会被
撤销机制错还原到已软删的旧卡上、别名表全行唯一索引让新别名被"冲突即跳过"静默吞掉。
清理边界(逐表核实过约束后定的,2026-07-18):
· muse_knowledge_draft 升格卡 → **软删**(deleted=TRUE,红线:知识数据绝不物删,可回滚)
· example_upgrade_window → 保留窗行(窗参数未变、from_chapter 锚幂等),仅重置 status=pending
· example_upgrade_alias → **硬删**该书行——uk(tenant_id,work_id,alias) 是全行唯一索引(非部分索引),
软删旧行会挡住重抽新 INSERT(ON CONFLICT DO NOTHING 静默吞)→ 别名判重空转
· example_upgrade_presence → 硬删该书行(运行状态留档,重抽全量重生成;undo 机制既有做法即硬删)
· example_upgrade_card_state → 硬删该书行(卡水位挂旧卡 draft_id,新卡新水位)
· example_upgrade_audit → 硬删该书行——undo_window 的还原 JOIN **不过滤 d.deleted**,
留着旧审计行,重抽期间任何同窗撤销都会把旧代软删卡错还原(污染)
· example_knowledge_embedding → **不动**:旧卡向量行靠判重召回 SQL 的 JOIN d.deleted=FALSE 天然排除,
软删状态保留可回溯;重抽边抽边嵌写新卡向量
用法:
reset_upgrade_work.py --work-id N 预览(只读统计,不写库)
reset_upgrade_work.py --work-id N --execute 真清(单事务,全清或全不清)
"""
import sys
import pathlib
import click
import psycopg
# 复用管线的连接与租户常量(与 parse_upgrade 同源)
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
from parse_llm import DSN, TENANT # noqa: E402
SOURCE_TYPE = "upgrade_book"
@click.command()
@click.option("--work-id", type=int, required=True)
@click.option("--execute", is_flag=True, help="真执行(默认只预览统计)")
def main(work_id, execute):
with psycopg.connect(DSN) as conn:
title = conn.execute("SELECT title FROM muse_content_work WHERE id=%s",
(work_id,)).fetchone()[0]
# 预览统计:各表将被处理的行数
cards = conn.execute(
"""SELECT count(*) FROM muse_knowledge_draft
WHERE tenant_id=%s AND work_id=%s AND source_type=%s AND deleted=FALSE""",
(TENANT, work_id, SOURCE_TYPE)).fetchone()[0]
wins = conn.execute(
"""SELECT count(*), count(*) FILTER (WHERE status='done') FROM example_upgrade_window
WHERE tenant_id=%s AND work_id=%s AND deleted=FALSE""",
(TENANT, work_id)).fetchone()
ali = conn.execute("SELECT count(*) FROM example_upgrade_alias WHERE tenant_id=%s AND work_id=%s",
(TENANT, work_id)).fetchone()[0]
pres = conn.execute("SELECT count(*) FROM example_upgrade_presence WHERE tenant_id=%s AND work_id=%s",
(TENANT, work_id)).fetchone()[0]
# card_state 无 work 冗余错?——有 work_id 列(建表即有);audit 无 work_id,靠 JOIN 卡定位
cs = conn.execute("SELECT count(*) FROM example_upgrade_card_state WHERE tenant_id=%s AND work_id=%s",
(TENANT, work_id)).fetchone()[0]
aud = conn.execute(
"""SELECT count(*) FROM example_upgrade_audit a
WHERE a.tenant_id=%s AND a.draft_id IN
(SELECT id FROM muse_knowledge_draft WHERE work_id=%s AND source_type=%s)""",
(TENANT, work_id, SOURCE_TYPE)).fetchone()[0]
click.echo(f"《{title}》work={work_id} 重抽准备{'(执行)' if execute else '(预览)'}:")
click.echo(f" 软删活升格卡 {cards} | 重置窗 {wins[0]}(其中done {wins[1]}) | "
f"硬删 别名{ali} 留档{pres} 卡水位{cs} 审计{aud}")
if not execute:
click.echo(" (预览模式未写库;加 --execute 真清)")
return
# 单事务执行:全清或全不清(中途失败自动回滚,不留半清状态)
conn.execute(
"""UPDATE muse_knowledge_draft SET deleted=TRUE, updater='upgrade-reset'
WHERE tenant_id=%s AND work_id=%s AND source_type=%s AND deleted=FALSE""",
(TENANT, work_id, SOURCE_TYPE))
conn.execute(
"""UPDATE example_upgrade_window SET status='pending', error_message=NULL,
updater='upgrade-reset' WHERE tenant_id=%s AND work_id=%s AND deleted=FALSE""",
(TENANT, work_id))
# 审计先删(靠卡 JOIN 定位,卡还查得到——虽然刚软删,JOIN 不看 deleted)
conn.execute(
"""DELETE FROM example_upgrade_audit
WHERE tenant_id=%s AND draft_id IN
(SELECT id FROM muse_knowledge_draft WHERE work_id=%s AND source_type=%s)""",
(TENANT, work_id, SOURCE_TYPE))
conn.execute("DELETE FROM example_upgrade_alias WHERE tenant_id=%s AND work_id=%s",
(TENANT, work_id))
conn.execute("DELETE FROM example_upgrade_presence WHERE tenant_id=%s AND work_id=%s",
(TENANT, work_id))
conn.execute("DELETE FROM example_upgrade_card_state WHERE tenant_id=%s AND work_id=%s",
(TENANT, work_id))
conn.commit()
# 清后复核:底册应为空、窗应全 pending
left = conn.execute(
"""SELECT count(*) FROM muse_knowledge_draft
WHERE tenant_id=%s AND work_id=%s AND source_type=%s AND deleted=FALSE""",
(TENANT, work_id, SOURCE_TYPE)).fetchone()[0]
pend = conn.execute(
"""SELECT count(*) FILTER (WHERE status='pending'), count(*) FROM example_upgrade_window
WHERE tenant_id=%s AND work_id=%s AND deleted=FALSE""", (TENANT, work_id)).fetchone()
click.echo(f" ✅ 清后复核:活升格卡 {left}(应0)| 窗 pending {pend[0]}/{pend[1]}(应全 pending)")
if __name__ == "__main__":
main()