zcbot/skills/ppt/scripts/accept_pages.py

145 lines
6.0 KiB
Python

"""accept_pages.py: 渲图验收的 verdict 记录器(与 svg_preview.py / 导出 gate 配套)。
流程:svg_preview.py 渲图并登记 `.build/acceptance.json` → 逐页 read/look_at_image
过目 → 用本脚本把看过且合格的页标 pass(有问题的标 fail 并写原因)→ 全部 pass
后 svg_to_pptx.py 才肯导出。
用法:
python accept_pages.py <project_dir> --status # 看各页验收状态
python accept_pages.py <project_dir> --pass P01_cover P02_toc
python accept_pages.py <project_dir> --pass-all # 全部标 pass
python accept_pages.py <project_dir> --fail P05_kpi --reason "大字压说明文字"
标 pass 的前提(逐页校验,不满足的页拒标并退非零):
- 该页渲过图(acceptance.json 有记录、PNG 还在);
- 渲图之后 svg_output 源没再改过(sha1 一致)—— 改了就得重跑 finalize +
svg_preview 再来,盲改混不过 gate。
"""
from __future__ import annotations
import argparse
import hashlib
import sys
try: # zcbot: Windows GBK 控制台兼容
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
from datetime import datetime
from pathlib import Path
from svg_preview import acceptance_path, load_acceptance, save_acceptance
def _sha1(path: Path) -> str:
return hashlib.sha1(path.read_bytes()).hexdigest()
def _page_problems(project_root: Path, stem: str, entry: dict) -> list[str]:
"""标 pass 前的逐页前提校验;返回问题列表(空 = 可标)。"""
problems = []
png = Path(entry.get("png") or "")
if not png.is_absolute():
png = project_root / png
if not entry.get("png") or not png.exists():
problems.append("渲染 PNG 不存在 —— 先跑 svg_preview.py")
src = project_root / "svg_output" / f"{stem}.svg"
if not src.exists():
src = Path(entry.get("source") or "")
if str(src) and not src.is_absolute():
src = project_root / src
if not src.exists():
problems.append("找不到源 SVG")
elif _sha1(src) != entry.get("source_sha1"):
problems.append("渲图后源 SVG 又改过 —— 重跑 finalize_svg + svg_preview 再标")
return problems
def main() -> int:
ap = argparse.ArgumentParser(description="标记渲图验收 verdict(配合导出 gate)")
ap.add_argument("project_dir", type=Path)
g = ap.add_mutually_exclusive_group(required=True)
g.add_argument("--status", action="store_true", help="打印各页验收状态")
g.add_argument("--pass", dest="mark_pass", nargs="+", metavar="PAGE",
help="把指定页(文件名去 .svg)标为 pass")
g.add_argument("--pass-all", action="store_true",
help="把所有已渲页标为 pass(仍逐页校验 sha/PNG)")
g.add_argument("--fail", dest="mark_fail", nargs="+", metavar="PAGE",
help="把指定页标为 fail(配 --reason)")
ap.add_argument("--reason", default="", help="fail 的原因,写进记录")
args = ap.parse_args()
root = args.project_dir
if not root.is_dir():
print(f"[fatal] 不是目录:{root}")
return 1
data = load_acceptance(root)
pages: dict = data["pages"]
svg_dir = root / "svg_output"
all_stems = sorted(p.stem for p in svg_dir.glob("*.svg")) if svg_dir.is_dir() else []
unrendered = [s for s in all_stems if s not in pages]
if args.status:
if not pages and not all_stems:
print(f"[status] 无验收记录也无 svg_output:{acceptance_path(root)}")
return 1
counts = {"pass": 0, "fail": 0, "pending": 0}
for stem in sorted(pages):
e = pages[stem]
v = e.get("verdict", "pending")
counts[v if v in counts else "pending"] += 1
extra = ""
if v == "pass" and _page_problems(root, stem, e):
extra = " <- 源已改动/PNG 丢失,导出 gate 会拒(需重渲重标)"
reason = f" ({e.get('reason')})" if e.get("reason") else ""
print(f" {v:8s} {stem}{reason}{extra}")
for stem in unrendered:
print(f" {'-':8s} {stem} <- 从未渲过")
print(f"[status] pass {counts['pass']} / fail {counts['fail']} / "
f"pending {counts['pending']} / 未渲 {len(unrendered)}")
return 0
now = datetime.now().isoformat(timespec="seconds")
if args.mark_fail:
missing = [s for s in args.mark_fail if s not in pages]
if missing:
print(f"[fatal] 无渲染记录的页:{', '.join(missing)}(先 svg_preview)")
return 1
for stem in args.mark_fail:
pages[stem].update(verdict="fail", verdict_at=now,
reason=args.reason or "unspecified")
print(f" [fail] {stem}" + (f":{args.reason}" if args.reason else ""))
save_acceptance(root, data)
return 0
targets = sorted(pages) if args.pass_all else args.mark_pass
missing = [s for s in targets if s not in pages]
if missing:
print(f"[fatal] 无渲染记录的页:{', '.join(missing)}(先 svg_preview,"
"页名 = SVG 文件名去 .svg)")
return 1
refused = 0
for stem in targets:
problems = _page_problems(root, stem, pages[stem])
if problems:
refused += 1
print(f" [refused] {stem}:{'; '.join(problems)}")
continue
pages[stem].update(verdict="pass", verdict_at=now)
pages[stem].pop("reason", None)
print(f" [pass] {stem}")
save_acceptance(root, data)
if unrendered and args.pass_all:
print(f" [warn] {len(unrendered)} 页从未渲过、不在记录里:"
f"{', '.join(unrendered[:8])}{'' if len(unrendered) > 8 else ''}")
if refused:
print(f"[accept_pages] {refused} 页被拒 —— 重跑 finalize_svg + svg_preview "
"后再标。")
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())