From 26f5dad58982ab729c0a2998d302822dbe3b6b8e Mon Sep 17 00:00:00 2001 From: zizi Date: Sat, 18 Jul 2026 09:38:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A1=86=E6=9E=B6:=20=E5=8D=87=E6=A0=BC?= =?UTF-8?q?=E5=85=A8=E9=87=8F=E9=87=8D=E6=8A=BD=E5=87=86=E5=A4=87=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E2=80=94=E2=80=94=E5=8D=A1=E8=BD=AF=E5=88=A0+?= =?UTF-8?q?=E4=B8=B2=E8=A1=8C=E7=8A=B6=E6=80=81=E8=A1=A8=E6=B8=85=E7=90=86?= =?UTF-8?q?(=E9=80=90=E8=A1=A8=E6=A0=B8=E5=AE=9E=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=E5=AE=9A=E8=BE=B9=E7=95=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../parse-book/scripts/reset_upgrade_work.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 .claude/skills/parse-book/scripts/reset_upgrade_work.py diff --git a/.claude/skills/parse-book/scripts/reset_upgrade_work.py b/.claude/skills/parse-book/scripts/reset_upgrade_work.py new file mode 100644 index 0000000..58aadb7 --- /dev/null +++ b/.claude/skills/parse-book/scripts/reset_upgrade_work.py @@ -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()