skills/ppt-station-skill/scripts/validate_job.py
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

125 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""原子工具Job JSON Schema 校验
校验 Job JSON 文件的合法性composer 模式额外校验布局名称和 source 引用。
用法:
python validate_job.py job.json
python validate_job.py job.json --dry-run # 校验 + 尝试连接数据源
输出 (stdout):
{"status": "ok", "mode": "...", "summary": {...}}
{"status": "error", "errors": [...]}
退出码: 0=通过, 1=失败
"""
import sys
import json
import argparse
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from ppt_station.models.job import Job
def main():
parser = argparse.ArgumentParser(description="Job JSON 校验工具")
parser.add_argument("job_json", help="Job JSON 文件路径")
parser.add_argument("--dry-run", action="store_true",
help="额外尝试连接数据源(不渲染)")
args = parser.parse_args()
job_path = Path(args.job_json)
if not job_path.exists():
print(json.dumps({"status": "error", "errors": [f"文件不存在: {job_path}"]},
ensure_ascii=False))
sys.exit(1)
# 1. 基础 Schema 校验
errors = []
try:
with open(job_path, "r", encoding="utf-8") as f:
raw = json.load(f)
job = Job(**raw)
except json.JSONDecodeError as e:
print(json.dumps({"status": "error", "errors": [f"JSON 语法错误: {e}"]},
ensure_ascii=False))
sys.exit(1)
except Exception as e:
print(json.dumps({"status": "error", "errors": [f"Schema 校验失败: {e}"]},
ensure_ascii=False))
sys.exit(1)
# 2. Composer 模式额外校验
if job.mode == "composer":
from ppt_station.composer.layouts import LAYOUT_REGISTRY
all_sources = set(job.datasources.keys())
if job.transforms:
all_sources.update(job.transforms.keys())
for i, page in enumerate(job.pages):
# 校验布局名称
if page.layout not in LAYOUT_REGISTRY:
available = ", ".join(LAYOUT_REGISTRY.keys())
errors.append(f"pages[{i}]: 未知布局 '{page.layout}'。可用: {available}")
# 校验 datasource/source 引用
if "datasource" in page.data:
if page.data["datasource"] not in all_sources:
errors.append(
f"pages[{i}]: datasource '{page.data['datasource']}' "
f"不存在于 datasources/transforms 中"
)
# source 值不在已知数据源中 → 视为脚注文本,不报错
# 校验嵌套 datasource/source 引用 (two_charts)
for key in ("left", "right"):
if key in page.data and isinstance(page.data[key], dict):
sub = page.data[key]
ds_ref = sub.get("datasource") or sub.get("source")
if ds_ref and ds_ref not in all_sources:
errors.append(
f"pages[{i}].data.{key}: datasource/source '{ds_ref}' "
f"不存在于 datasources/transforms 中"
)
if errors:
print(json.dumps({"status": "error", "errors": errors}, ensure_ascii=False))
sys.exit(1)
# 3. 构造摘要
summary = {"mode": job.mode, "datasources": list(job.datasources.keys())}
if job.mode == "template":
summary["slides"] = len(job.slides)
summary["template"] = job.template.path
elif job.mode == "composer":
summary["pages"] = len(job.pages)
summary["theme"] = job.theme or "jp_finance"
summary["layouts"] = [p.layout for p in job.pages]
if job.transforms:
summary["transforms"] = list(job.transforms.keys())
# 4. 可选 dry-run尝试连接数据源
if args.dry_run and job.datasources:
from ppt_station.connectors import ConnectorFactory
ds_status = {}
for name, ds in job.datasources.items():
try:
print(f"尝试连接: {name}", file=sys.stderr)
df = ConnectorFactory.load_data(name, ds)
ds_status[name] = {"ok": True, "rows": len(df), "columns": list(df.columns)}
except Exception as e:
ds_status[name] = {"ok": False, "error": str(e)}
summary["datasource_check"] = ds_status
print(json.dumps({"status": "ok", "summary": summary}, ensure_ascii=False))
if __name__ == "__main__":
main()