McKinsey PPT Design — Harness Framework Version : 2.3.3-harness-v2 · Engine : MckEngine (python-pptx) · Author : likaku Required tools : Read, Write, Bash · Requires : --- 常见崩坏模式(每次生成前必读,先于 HARD RULES) 告诉 AI 要做什么效果一般,告诉它前人最容易在哪里塌效果更好。 以下三种反模式均已在真实执行中被验证,每次必须主动警惕。 反模式 1:口头宣布"门禁通过"(最常见) 错误做法 : 「S4 QA 共 7 个 errors,判断均为 engine 设计行为,门禁通过,进入 S5」 问题所在 : 是由 AI 口头判断的,不是由程序派生的。无论理由多充分,这句话都是 AI 在给自己写完成证书。 正确做法 : 1. 执行 2. 读取 3. 只有 中 时,才能进入 S5 4. 如果 ,修复 列表中的问题,重新渲染,再次执行 gate check --- 反模式 2:S3 门禁"脑子里过一遍"就算通过 错误做法 : 「S3 内容门禁预检:API 格式正确,字数在限制内,通过,进入 S4」(没有执行任何代码) 问题所在 :今天真实发生的 3 个 API 格式错误( / / 参数格式),靠脑子过是过…

)\n small_texts = []\n for shape in shapes:\n if not hasattr(shape, \"left\") or shape.left is None:\n continue\n if not shape.has_text_frame:\n continue\n text = shape.text_frame.text.strip()\n if not text:\n continue\n # Skip page numbers\n if page_num_re.match(text):\n continue\n w = (shape.width or 0) / 914400\n h = (shape.height or 0) / 914400\n top = (shape.top or 0) / 914400\n # Skip shapes in the footer area (source line, page number region)\n if top > 6.8:\n continue\n if h \u003c= 0.5 and w \u003c= 2.5 and len(text) \u003c= 20:\n left, top_emu, right, bottom = _bbox(shape)\n small_texts.append((shape, left, top_emu, right, text))\n\n # Check each small text — if right edge exceeds content area, flag it\n for shape, left, top, right, text in small_texts:\n if right > content_right + OVERFLOW_TOLERANCE:\n overflow = (right - content_right) / 914400\n self.issues.append(QAIssue(\n slide_num=num,\n severity=Severity.ERROR,\n category=\"chart_legend_overflow\",\n message=f\"Legend/label '{text}' overflows content area RIGHT by {overflow:.2f}\\\"\",\n shape_name=getattr(shape, \"name\", \"\"),\n details={\"right_edge_in\": right / 914400, \"content_right_in\": content_right / 914400},\n ))\n\n # ── Check 7: Connector Usage (Guard Rail #1) ─────────────────────\n def _check_connectors(self, num: int, slide):\n \"\"\"Check for connector shapes that can cause file corruption.\"\"\"\n xml = slide._element.xml\n if \"\u003cp:cxnSp\" in xml:\n # Count connectors\n count = xml.count(\"\u003cp:cxnSp\")\n self.issues.append(QAIssue(\n slide_num=num,\n severity=Severity.ERROR,\n category=\"guard_rail\",\n message=f\"Slide contains {count} connector(s) — risk of file corruption (Guard Rail #1)\",\n details={\"connector_count\": count},\n ))\n\n # ── Check 7: p:style remnants ─────────────────────────────────────\n def _check_global(self):\n \"\"\"Global checks across the entire file.\"\"\"\n # Check for p:style remnants in XML\n for idx, slide in enumerate(self.prs.slides, 1):\n xml = slide._element.xml\n pstyle_count = xml.count(\"\u003cp:style\")\n if pstyle_count > 0:\n self.issues.append(QAIssue(\n slide_num=idx,\n severity=Severity.WARNING,\n category=\"guard_rail\",\n message=f\"Found {pstyle_count} \u003cp:style> remnant(s) — run full_cleanup()\",\n details={\"pstyle_count\": pstyle_count},\n ))\n\n # ── Slide score calculation ───────────────────────────────────────\n def _calc_slide_score(self, issues: List[QAIssue]) -> int:\n \"\"\"Calculate quality score for a slide (0-100).\"\"\"\n score = 100\n for issue in issues:\n if issue.severity == Severity.ERROR:\n if issue.category == \"body_overflow\":\n score -= 25\n elif issue.category == \"text_overflow\":\n score -= 20\n elif issue.category == \"guard_rail\":\n score -= 30\n else:\n score -= 15\n elif issue.severity == Severity.WARNING:\n if issue.category == \"dead_whitespace\":\n score -= 10\n elif issue.category == \"text_overflow\":\n score -= 8\n elif issue.category == \"shape_overlap\":\n score -= 10\n else:\n score -= 5\n else:\n score -= 1\n return max(0, score)\n\n\n# ── CLI entry point ───────────────────────────────────────────────────\ndef analyze(filepath: str, json_out: str = None, verbose: bool = True) -> QAReport:\n \"\"\"\n Convenience function: analyze a PPTX and optionally save JSON report.\n\n Args:\n filepath: Path to .pptx file\n json_out: Optional path for JSON report output\n verbose: Print summary to stdout\n\n Returns:\n QAReport\n \"\"\"\n qa = PptQA(filepath)\n report = qa.run()\n if verbose:\n report.print_summary()\n if json_out:\n report.to_json(json_out)\n return report\n\n\nif __name__ == \"__main__\":\n import sys\n if len(sys.argv) \u003c 2:\n print(\"Usage: python -m mck_ppt.qa \u003cpath.pptx> [--json report.json]\")\n sys.exit(1)\n\n pptx_path = sys.argv[1]\n json_path = None\n if \"--json\" in sys.argv:\n idx = sys.argv.index(\"--json\")\n if idx + 1 \u003c len(sys.argv):\n json_path = sys.argv[idx + 1]\n\n report = analyze(pptx_path, json_out=json_path)\n sys.exit(0 if report.passed else 1)","content_type":"text/x-python; charset=utf-8","language":"python","size":38914,"content_sha256":"f802601fcf272ce500de56996fb527ae89280a76823f9d1aff5afc4a8297eb1d"},{"filename":"mck_ppt/review.py","content":"# Copyright 2024-2026 Kaku Li (https://github.com/likaku)\n# Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.\n# Part of \"Mck-ppt-design-skill\" (McKinsey PPT Design Framework).\n# NOTICE: This file must be retained in all copies or substantial portions.\n#\n\"\"\"\nPost-generation Review + Auto-fix Pipeline for mck_ppt.\n\nFour-stage flow:\n 1. Page Brief — structure content into page_objective / one_message / mece_buckets\n 2. Dual QA — Narrative QA (content) + Layout QA (geometry, via qa.py)\n 3. Auto-fix — priority chain: 去冗余 → 统一语言 → 压缩句式 → 重构层级 → 字号微调\n (NO layout/布局 changes — user constraint)\n 4. Gate — 0 ERROR = pass; otherwise iterate (max N rounds)\n\nUsage:\n from mck_ppt.review import SlideReviewer\n reviewer = SlideReviewer(\"output/deck.pptx\")\n report = reviewer.run() # read-only audit\n report.print_summary()\n\n # Auto-fix (mutates the file in-place, up to max_rounds)\n from mck_ppt.review import AutoFixPipeline\n result = AutoFixPipeline(\"output/deck.pptx\").run(max_rounds=3)\n print(result)\n\"\"\"\n\nfrom __future__ import annotations\n\nimport copy\nimport math\nimport os\nimport re\nfrom dataclasses import dataclass, field\nfrom typing import List, Optional, Tuple, Dict\n\nfrom pptx import Presentation\nfrom pptx.util import Inches, Pt, Emu\nfrom pptx.oxml.ns import qn\n\nfrom .qa import (\n PptQA, QAReport, QAIssue, Severity,\n _estimate_text_height, _bbox,\n SW, SH, LM, CW, CONTENT_TOP, SOURCE_Y,\n OVERFLOW_TOLERANCE, TEXT_OVERFLOW_LINE_RATIO,\n MIN_FONT_SIZE,\n)\nfrom .constants import (\n BODY_SIZE, SMALL_SIZE, FOOTNOTE_SIZE, ACTION_TITLE_SIZE,\n SUB_HEADER_SIZE, EMPHASIS_SIZE,\n)\n\n\n# ═══════════════════════════════════════════════════════════════\n# Content density limits (per-layout experience caps)\n# ═══════════════════════════════════════════════════════════════\n\n# Max characters per text box before we flag as \"too dense\"\n# Keyed by approximate box height in inches (rounded down)\nCHAR_DENSITY_LIMITS = {\n 0.2: 15, # tiny label boxes\n 0.3: 30,\n 0.4: 50,\n 0.5: 70,\n 0.6: 90,\n 0.8: 130,\n 1.0: 180,\n 1.5: 300,\n 2.0: 450,\n 3.0: 700,\n 5.0: 1200,\n}\n\n# Title length warning thresholds\nACTION_TITLE_MAX_CHARS = 45\nSUBTITLE_MAX_CHARS = 30\n\n# Font size guard rails for autofix\nTITLE_MIN_PT = 20 # action titles cannot shrink below this\nBODY_MIN_PT = 11 # body text floor\nSMALL_MIN_PT = 9 # small/footnote floor\n\n\n# ═══════════════════════════════════════════════════════════════\n# Narrative QA Issue\n# ═══════════════════════════════════════════════════════════════\n\n@dataclass\nclass NarrativeIssue:\n slide_num: int\n severity: str # ERROR / WARNING / INFO\n category: str # e.g. \"no_action_title\", \"lang_mix\", \"density\"\n message: str\n shape_name: str = \"\"\n suggestion: str = \"\"\n\n\n@dataclass\nclass NarrativeReport:\n total_slides: int\n issues: List[NarrativeIssue] = field(default_factory=list)\n\n @property\n def errors(self):\n return [i for i in self.issues if i.severity == Severity.ERROR]\n\n @property\n def warnings(self):\n return [i for i in self.issues if i.severity == Severity.WARNING]\n\n def print_summary(self):\n print(f\"\\n{'─'*60}\")\n print(f\" Narrative QA — {self.total_slides} slides\")\n print(f\" Errors: {len(self.errors)} Warnings: {len(self.warnings)}\")\n print(f\"{'─'*60}\")\n for iss in self.issues:\n icon = {\"ERROR\": \"❌\", \"WARNING\": \"⚠️ \", \"INFO\": \"ℹ️ \"}[iss.severity]\n print(f\" S{iss.slide_num} {icon} [{iss.category}] {iss.message}\")\n if iss.suggestion:\n print(f\" → {iss.suggestion}\")\n print()\n\n\n# ═══════════════════════════════════════════════════════════════\n# NarrativeReviewer — content-level checks\n# ═══════════════════════════════════════════════════════════════\n\nclass NarrativeReviewer:\n \"\"\"Checks each slide for narrative quality issues.\"\"\"\n\n def __init__(self, filepath: str):\n self.filepath = filepath\n self.prs = Presentation(filepath)\n\n def run(self) -> NarrativeReport:\n issues: List[NarrativeIssue] = []\n for idx, slide in enumerate(self.prs.slides, 1):\n issues.extend(self._check_slide(idx, slide))\n return NarrativeReport(\n total_slides=len(self.prs.slides),\n issues=issues,\n )\n\n def _check_slide(self, num: int, slide) -> List[NarrativeIssue]:\n issues = []\n shapes = list(slide.shapes)\n all_text = self._collect_text(shapes)\n\n # 1. Check for mixed Chinese/English where unnecessary\n issues.extend(self._check_lang_mix(num, shapes))\n\n # 2. Check text density per box\n issues.extend(self._check_density(num, shapes))\n\n # 3. Check title length\n issues.extend(self._check_title_length(num, shapes))\n\n return issues\n\n def _collect_text(self, shapes) -> str:\n parts = []\n for s in shapes:\n if s.has_text_frame:\n parts.append(s.text_frame.text)\n return \"\\n\".join(parts)\n\n # ── Mixed language check ──────────────────────────────────\n _EN_JARGON_RE = re.compile(\n r'\\b(selling motion|business acumen|true leader|value proposition|'\n r'key takeaway|stakeholder|playbook|deal review|pipeline|'\n r'account lead|solution architect|domain expert|customer success|'\n r'partner ecosystem|extended capability|value realization|'\n r'industry coe|orchestrator|value architect)\\b',\n re.IGNORECASE,\n )\n\n def _check_lang_mix(self, num: int, shapes) -> List[NarrativeIssue]:\n issues = []\n for s in shapes:\n if not s.has_text_frame:\n continue\n text = s.text_frame.text\n # Only flag if the text is predominantly Chinese (>30% CJK)\n cjk = sum(1 for c in text if '\\u4e00' \u003c= c \u003c= '\\u9fff')\n if len(text) == 0 or cjk / max(len(text), 1) \u003c 0.2:\n continue\n matches = self._EN_JARGON_RE.findall(text)\n if matches:\n issues.append(NarrativeIssue(\n slide_num=num,\n severity=Severity.INFO,\n category=\"lang_mix\",\n message=f\"中英混用术语: {', '.join(set(m.lower() for m in matches))}\",\n shape_name=getattr(s, \"name\", \"\"),\n suggestion=\"仅供参考,英文专有名词可保留原文\",\n ))\n return issues\n\n # ── Text density per box ──────────────────────────────────\n def _check_density(self, num: int, shapes) -> List[NarrativeIssue]:\n issues = []\n for s in shapes:\n if not s.has_text_frame or not s.text_frame.text.strip():\n continue\n if s.height is None or s.height \u003c= 0:\n continue\n box_h_in = s.height / 914400\n text = s.text_frame.text.strip()\n char_count = len(text)\n\n # Find applicable limit\n limit = None\n for h_thresh in sorted(CHAR_DENSITY_LIMITS.keys()):\n if box_h_in >= h_thresh:\n limit = CHAR_DENSITY_LIMITS[h_thresh]\n if limit and char_count > limit * 1.3:\n issues.append(NarrativeIssue(\n slide_num=num,\n severity=Severity.WARNING,\n category=\"density\",\n message=f\"文本密度过高: {char_count}字 / {box_h_in:.2f}\\\" 高度框 (建议≤{limit})\",\n shape_name=getattr(s, \"name\", \"\"),\n suggestion=\"考虑精简文案或拆分内容\",\n ))\n return issues\n\n # ── Title length ──────────────────────────────────────────\n def _check_title_length(self, num: int, shapes) -> List[NarrativeIssue]:\n issues = []\n for s in shapes:\n if not s.has_text_frame:\n continue\n for para in s.text_frame.paragraphs:\n for run in para.runs:\n if run.font.size and run.font.size.pt >= 20:\n text = para.text.strip()\n if len(text) > ACTION_TITLE_MAX_CHARS:\n issues.append(NarrativeIssue(\n slide_num=num,\n severity=Severity.WARNING,\n category=\"title_long\",\n message=f\"标题过长: {len(text)}字 (建议≤{ACTION_TITLE_MAX_CHARS})\",\n shape_name=getattr(s, \"name\", \"\"),\n suggestion=\"缩短标题,把补充信息移到副标题或正文\",\n ))\n break # one check per shape\n break\n return issues\n\n\n# ═══════════════════════════════════════════════════════════════\n# SlideReviewer — combined Narrative + Layout QA\n# ═══════════════════════════════════════════════════════════════\n\n@dataclass\nclass CombinedReport:\n filepath: str\n layout_report: QAReport\n narrative_report: NarrativeReport\n\n @property\n def total_errors(self) -> int:\n return len(self.layout_report.errors) + len(self.narrative_report.errors)\n\n @property\n def passed(self) -> bool:\n return len(self.layout_report.errors) == 0\n\n def print_summary(self):\n print(f\"\\n{'='*70}\")\n print(f\" Combined Review: {os.path.basename(self.filepath)}\")\n print(f\" Layout score: {self.layout_report.overall_score}/100\")\n print(f\" Layout errors: {len(self.layout_report.errors)} \"\n f\"warnings: {len(self.layout_report.warnings)}\")\n print(f\" Narrative errors: {len(self.narrative_report.errors)} \"\n f\"warnings: {len(self.narrative_report.warnings)}\")\n print(f\" Gate: {'✅ PASS' if self.passed else '❌ FAIL'}\")\n print(f\"{'='*70}\")\n self.layout_report.print_summary()\n self.narrative_report.print_summary()\n\n\nclass SlideReviewer:\n \"\"\"Run dual QA (Layout + Narrative) on a generated pptx.\"\"\"\n\n def __init__(self, filepath: str):\n self.filepath = filepath\n\n def run(self) -> CombinedReport:\n layout_report = PptQA(self.filepath).run()\n narrative_report = NarrativeReviewer(self.filepath).run()\n return CombinedReport(\n filepath=self.filepath,\n layout_report=layout_report,\n narrative_report=narrative_report,\n )\n\n\n# ═══════════════════════════════════════════════════════════════\n# AutoFixPipeline — fix text overflow by priority chain\n#\n# Priority (no layout changes, per user constraint):\n# 1. 去冗余 — remove redundant phrasing, weak info\n# 2. 统一语言 — replace unnecessary English jargon with Chinese\n# 3. 压缩句式 — shorten sentences\n# 4. 重构层级 — restructure bullets (within same box)\n# 5. 字号微调 — shrink font within guard rails\n#\n# Works directly on the .pptx shapes (post-generation fix).\n# ═══════════════════════════════════════════════════════════════\n\n# Mapping of English jargon → Chinese replacement\n# DISABLED: English domain terms (selling motion, business acumen, playbook,\n# deal review, etc.) are intentional professional vocabulary — do NOT translate.\n_LANG_REPLACEMENTS = {}\n\n# Redundancy patterns: (regex, replacement_or_empty)\n_REDUNDANCY_PATTERNS = [\n # Remove weak hedging\n (re.compile(r'(?:从某种意义上说|在一定程度上|可以说是)'), ''),\n # Remove trailing \"等\" after a clear list\n (re.compile(r'([^、]+、[^、]+)等(?=[。,])'), r'\\1'),\n # Collapse \"进行XX\" → \"XX\"\n (re.compile(r'进行([\\u4e00-\\u9fff]{2,4})'), r'\\1'),\n # Remove \"的话\"\n (re.compile(r'的话(?=[,。;])'), ''),\n]\n\n# Sentence compression: simplify common verbose patterns\n_COMPRESSION_PATTERNS = [\n # \"因为A所以B\" → \"A → B\" (for bullet-style text)\n (re.compile(r'因为(.{4,20})(?:,|,)所以(.{4,20})'), r'\\1 → \\2'),\n # \"不仅...而且...\" → combine\n (re.compile(r'不仅(.{3,15})(?:,|,)而且(.{3,15})'), r'\\1,且\\2'),\n # \"通过...来实现...\" simplify\n (re.compile(r'通过(.{3,15})来实现(.{3,15})'), r'\\1实现\\2'),\n]\n\n\nclass AutoFixPipeline:\n \"\"\"Fix text overflow issues in a generated .pptx, in-place.\n\n Only modifies text content and font sizes.\n Does NOT change layout, shape positions, or box dimensions.\n \"\"\"\n\n def __init__(self, filepath: str):\n self.filepath = filepath\n\n def run(self, max_rounds: int = 3, verbose: bool = True) -> CombinedReport:\n \"\"\"Iterate: fix → re-check → fix ... up to max_rounds.\n After overflow fixes converge, harmonize peer font groups.\n \"\"\"\n # Phase 1: Fix text overflow errors iteratively\n for round_num in range(1, max_rounds + 1):\n if verbose:\n print(f\"\\n{'━'*60}\")\n print(f\" Auto-fix round {round_num}/{max_rounds}\")\n print(f\"{'━'*60}\")\n\n # Run QA\n report = SlideReviewer(self.filepath).run()\n\n layout_errors = report.layout_report.errors\n text_overflow_errors = [\n e for e in layout_errors if e.category == \"text_overflow\"\n ]\n\n if not text_overflow_errors:\n if verbose:\n print(\" ✅ No text overflow errors.\")\n break\n\n if verbose:\n print(f\" Found {len(text_overflow_errors)} text overflow error(s).\")\n\n # Apply fixes\n fixes_applied = self._apply_fixes(text_overflow_errors, verbose)\n\n if not fixes_applied:\n if verbose:\n print(\" ⚠️ No more fixes applicable. Stopping iteration.\")\n break\n\n # Phase 2: Harmonize peer font groups\n if verbose:\n print(f\"\\n{'━'*60}\")\n print(f\" Peer font harmonization\")\n print(f\"{'━'*60}\")\n harmonized = self._harmonize_peer_fonts(verbose)\n\n # Phase 3: Final check\n report = SlideReviewer(self.filepath).run()\n if verbose and report.passed:\n print(\" ✅ Pipeline PASSED.\")\n return report\n\n def _apply_fixes(self, overflow_errors: List[QAIssue], verbose: bool) -> int:\n \"\"\"Apply the priority chain to fix overflows. Returns number of fixes applied.\"\"\"\n prs = Presentation(self.filepath)\n fixes_count = 0\n\n for err in overflow_errors:\n slide_idx = err.slide_num - 1\n if slide_idx >= len(prs.slides):\n continue\n slide = prs.slides[slide_idx]\n shape = self._find_shape(slide, err.shape_name)\n if shape is None or not shape.has_text_frame:\n continue\n\n tf = shape.text_frame\n original_text = tf.text\n box_h = shape.height\n box_w = shape.width\n\n # Priority 1: Remove redundancy\n fixed = self._fix_redundancy(tf)\n if fixed and self._check_fits(tf, box_w, box_h):\n fixes_count += 1\n if verbose:\n print(f\" S{err.slide_num} [{err.shape_name}] ✂️ 去冗余 → 通过\")\n continue\n\n # Priority 2: Unify language (replace English jargon)\n fixed = self._fix_language(tf)\n if fixed and self._check_fits(tf, box_w, box_h):\n fixes_count += 1\n if verbose:\n print(f\" S{err.slide_num} [{err.shape_name}] 🔤 统一语言 → 通过\")\n continue\n\n # Priority 3: Compress sentences\n fixed = self._fix_compress(tf)\n if fixed and self._check_fits(tf, box_w, box_h):\n fixes_count += 1\n if verbose:\n print(f\" S{err.slide_num} [{err.shape_name}] 📐 压缩句式 → 通过\")\n continue\n\n # Priority 4: Restructure (trim bullets, shorten)\n fixed = self._fix_restructure(tf)\n if fixed and self._check_fits(tf, box_w, box_h):\n fixes_count += 1\n if verbose:\n print(f\" S{err.slide_num} [{err.shape_name}] 🔄 重构层级 → 通过\")\n continue\n\n # Priority 5: Font size micro-adjust\n fixed = self._fix_font_size(tf, box_w, box_h)\n if fixed:\n fixes_count += 1\n if verbose:\n print(f\" S{err.slide_num} [{err.shape_name}] 🔠 字号微调 → 通过\")\n continue\n\n if verbose:\n print(f\" S{err.slide_num} [{err.shape_name}] ⚠️ 所有策略未能解决溢出\")\n\n if fixes_count > 0:\n prs.save(self.filepath)\n # Re-run full_cleanup to sanitize\n try:\n from .core import full_cleanup\n full_cleanup(self.filepath)\n except Exception:\n pass\n\n return fixes_count\n\n # ── Peer font harmonization ──────────────────────────────\n PEER_Y_TOLERANCE = Emu(18288) # 0.02\" — match qa.py\n\n def _harmonize_peer_fonts(self, verbose: bool) -> int:\n \"\"\"After overflow fixes, ensure peer groups (same Y position)\n share the same font size. Uses min(sizes) so no new overflow.\n Returns number of shapes adjusted.\n \"\"\"\n prs = Presentation(self.filepath)\n total_adjusted = 0\n\n for slide_idx, slide in enumerate(prs.slides):\n # Collect text shapes with their resolved font info\n entries = [] # (top_emu, shape, para_font_size_pt, run_font_size_pt)\n for s in slide.shapes:\n if not s.has_text_frame or not s.text_frame.text.strip():\n continue\n if s.top is None:\n continue\n for para in s.text_frame.paragraphs:\n if not para.text.strip():\n continue\n p_size = para.font.size.pt if para.font.size else None\n r_size = None\n for run in para.runs:\n if run.font.size:\n r_size = run.font.size.pt\n break\n effective = r_size or p_size\n if effective is not None:\n entries.append((s.top, s, p_size, r_size, effective))\n break # first paragraph only\n\n if len(entries) \u003c 3:\n continue\n\n # Group by Y position\n entries.sort(key=lambda e: e[0])\n groups: list[list] = []\n for entry in entries:\n placed = False\n for g in groups:\n if abs(entry[0] - g[0][0]) \u003c= self.PEER_Y_TOLERANCE:\n g.append(entry)\n placed = True\n break\n if not placed:\n groups.append([entry])\n\n # Harmonize each peer group (≥3 members)\n for g in groups:\n if len(g) \u003c 3:\n continue\n sizes = [e[4] for e in g]\n if len(set(sizes)) \u003c= 1:\n continue # already uniform\n\n target_size = min(sizes) # use smallest to avoid new overflows\n for top_emu, shape, p_size, r_size, eff_size in g:\n if eff_size == target_size:\n continue\n # Adjust paragraph-level font\n for para in shape.text_frame.paragraphs:\n if para.font.size and para.font.size.pt != target_size:\n para.font.size = Pt(target_size)\n total_adjusted += 1\n for run in para.runs:\n if run.font.size and run.font.size.pt != target_size:\n run.font.size = Pt(target_size)\n break # first paragraph only\n\n if verbose:\n names = [getattr(e[1], 'name', '?') for e in g]\n texts = [e[1].text_frame.text.strip()[:20] for e in g]\n print(f\" S{slide_idx+1} y≈{g[0][0]/914400:.2f}\\\" \"\n f\"统一为 {target_size}pt \"\n f\"({', '.join(f'{t}' for t in texts)})\")\n\n if total_adjusted > 0:\n prs.save(self.filepath)\n try:\n from .core import full_cleanup\n full_cleanup(self.filepath)\n except Exception:\n pass\n if verbose:\n print(f\" 已调整 {total_adjusted} 个 shape 的字号以保持同级一致\")\n else:\n if verbose:\n print(\" ✅ 所有同级 peer group 字号已一致\")\n\n return total_adjusted\n\n # ── Shape finder ──────────────────────────────────────────\n def _find_shape(self, slide, shape_name: str):\n for s in slide.shapes:\n if getattr(s, \"name\", \"\") == shape_name:\n return s\n return None\n\n # ── Fit checker ───────────────────────────────────────────\n def _check_fits(self, tf, box_w, box_h) -> bool:\n est = _estimate_text_height(tf, box_w)\n return est \u003c= box_h * TEXT_OVERFLOW_LINE_RATIO\n\n # ── Priority 1: Remove redundancy ─────────────────────────\n def _fix_redundancy(self, tf) -> bool:\n changed = False\n for para in tf.paragraphs:\n for run in para.runs:\n original = run.text\n text = original\n for pattern, replacement in _REDUNDANCY_PATTERNS:\n text = pattern.sub(replacement, text)\n if text != original:\n run.text = text\n changed = True\n return changed\n\n # ── Priority 2: Unify language ────────────────────────────\n def _fix_language(self, tf) -> bool:\n changed = False\n for para in tf.paragraphs:\n for run in para.runs:\n text = run.text\n original = text\n for en, zh in _LANG_REPLACEMENTS.items():\n text = re.sub(re.escape(en), zh, text, flags=re.IGNORECASE)\n if text != original:\n run.text = text\n changed = True\n return changed\n\n # ── Priority 3: Compress sentences ────────────────────────\n def _fix_compress(self, tf) -> bool:\n changed = False\n for para in tf.paragraphs:\n for run in para.runs:\n original = run.text\n text = original\n for pattern, replacement in _COMPRESSION_PATTERNS:\n text = pattern.sub(replacement, text)\n # Also: strip trailing punctuation repetition\n text = re.sub(r'[。]{2,}', '。', text)\n text = re.sub(r'[,]{2,}', ',', text)\n if text != original:\n run.text = text\n changed = True\n return changed\n\n # ── Priority 4: Restructure (within same box) ─────────────\n def _fix_restructure(self, tf) -> bool:\n \"\"\"Trim each paragraph to fit better:\n - Remove trailing explanatory clauses after semicolons\n - If a bullet has >2 sub-clauses, keep only the first two\n \"\"\"\n changed = False\n for para in tf.paragraphs:\n for run in para.runs:\n text = run.text\n original = text\n\n # If text has semicolons with 3+ clauses, trim to first 2\n if ';' in text:\n parts = text.split(';')\n if len(parts) > 2:\n text = ';'.join(parts[:2])\n changed = True\n\n # If text has commas with many clauses (>3), trim\n if text.count(',') > 3:\n parts = text.split(',')\n if len(parts) > 4:\n text = ','.join(parts[:3]) + '。'\n changed = True\n\n if text != original:\n run.text = text\n return changed\n\n # ── Priority 5: Font size micro-adjust ────────────────────\n def _fix_font_size(self, tf, box_w, box_h) -> bool:\n \"\"\"Shrink font by 1pt at a time, respecting guard rails.\n Handles both run-level and paragraph-level font sizes.\n \"\"\"\n changed = False\n for attempt in range(4): # max 4pt reduction\n if self._check_fits(tf, box_w, box_h):\n return changed\n\n shrunk_any = False\n for para in tf.paragraphs:\n # Check paragraph-level font size\n if para.font.size:\n current_pt = para.font.size.pt\n if current_pt >= 20:\n floor = TITLE_MIN_PT\n elif current_pt >= 13:\n floor = BODY_MIN_PT\n else:\n floor = SMALL_MIN_PT\n if current_pt > floor:\n para.font.size = Pt(current_pt - 1)\n shrunk_any = True\n changed = True\n\n # Also check run-level\n for run in para.runs:\n if run.font.size:\n current_pt = run.font.size.pt\n if current_pt >= 20:\n floor = TITLE_MIN_PT\n elif current_pt >= 13:\n floor = BODY_MIN_PT\n else:\n floor = SMALL_MIN_PT\n if current_pt > floor:\n run.font.size = Pt(current_pt - 1)\n shrunk_any = True\n changed = True\n\n if not shrunk_any:\n break # all fonts already at floor\n\n return changed\n\n\n# ═══════════════════════════════════════════════════════════════\n# CLI entry\n# ═══════════════════════════════════════════════════════════════\n\ndef review(filepath: str) -> CombinedReport:\n \"\"\"Read-only dual review.\"\"\"\n return SlideReviewer(filepath).run()\n\n\ndef autofix(filepath: str, max_rounds: int = 3) -> CombinedReport:\n \"\"\"Review + auto-fix pipeline.\"\"\"\n return AutoFixPipeline(filepath).run(max_rounds=max_rounds)\n\n\nif __name__ == \"__main__\":\n import sys\n if len(sys.argv) \u003c 2:\n print(\"Usage: python -m mck_ppt.review \u003cpath.pptx> [--fix]\")\n sys.exit(1)\n path = sys.argv[1]\n if \"--fix\" in sys.argv:\n result = autofix(path)\n else:\n result = review(path)\n result.print_summary()\n sys.exit(0 if result.passed else 1)","content_type":"text/x-python; charset=utf-8","language":"python","size":29301,"content_sha256":"13392cd31ea2b8753518aa4c94a71ee72fd382695967294bd24e0b4a6a46ffca"},{"filename":"mck_ppt/storylines/__init__.py","content":"# Copyright 2024-2026 Kaku Li (https://github.com/likaku)\n# Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.\n# Part of \"Mck-ppt-design-skill\" (McKinsey PPT Design Framework).\n# NOTICE: This file must be retained in all copies or substantial portions.\n#\n","content_type":"text/x-python; charset=utf-8","language":"python","size":279,"content_sha256":"405f035b5d415a2020396e3e3891c0a5d45b4e930dd2612bcd4c7831cc1fce19"},{"filename":"mck_ppt/storylines/ai_enterprise.py","content":"# Copyright 2024-2026 Kaku Li (https://github.com/likaku)\n# Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.\n# Part of \"Mck-ppt-design-skill\" (McKinsey PPT Design Framework).\n# NOTICE: This file must be retained in all copies or substantial portions.\n#\n\"\"\"AI Enterprise Applications — Complete Chinese Storyline (33 slides).\n\nA comprehensive strategic briefing on AI applications across industries,\nusing 20+ different MckEngine template types for visual variety.\n\nUsage:\n from mck_ppt.deck_builder import DeckBuilder\n from mck_ppt.storylines import ai_enterprise\n DeckBuilder.build(ai_enterprise.STORYLINE, 'output/ai_enterprise.pptx')\n\"\"\"\nfrom mck_ppt.constants import (\n NAVY, ACCENT_BLUE, ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED,\n LIGHT_BLUE, LIGHT_GREEN, LIGHT_ORANGE, LIGHT_RED,\n DARK_GRAY, BG_GRAY, WHITE,\n)\n\n\nSTORYLINE = [\n # ═══════════════════════════════════════════════════════\n # SLIDE 1 — 封面\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'cover',\n 'data': {\n 'title': '人工智能企业应用战略\\n从技术趋势到落地实践',\n 'subtitle': '面向C-Suite的AI投资决策框架',\n 'date': '2026年3月',\n },\n },\n\n # ═══════════════════════════════════════════════════════\n # SLIDE 2 — 目录\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'toc',\n 'data': {\n 'title': '目录',\n 'items': [\n ('01', 'AI技术全景与市场格局', '核心技术、市场规模、发展趋势'),\n ('02', '七大企业应用领域', '智能客服、供应链、制造、金融、医疗、自动驾驶、办公'),\n ('03', '大语言模型与NLP', 'GPT/Claude生态、企业部署策略、RAG架构'),\n ('04', '计算机视觉与智能制造', '工业质检、自动驾驶、医学影像'),\n ('05', 'AI实施框架与行动计划', '风险评估、ROI模型、90天落地路线图'),\n ],\n },\n },\n\n # ═══════════════════════════════════════════════════════\n # CHAPTER 1: AI技术全景\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'section_divider',\n 'data': {\n 'section_label': '01',\n 'title': 'AI技术全景与市场格局',\n 'subtitle': '从学术突破到产业革命的关键拐点',\n },\n },\n\n # SLIDE 4 — AI发展里程碑\n {\n 'type': 'timeline',\n 'data': {\n 'title': 'AI技术发展的六个关键里程碑',\n 'milestones': [\n ('1956', '达特茅斯会议\\nAI概念诞生'),\n ('1997', 'DeepBlue击败\\n国际象棋冠军'),\n ('2012', 'AlexNet引爆\\n深度学习革命'),\n ('2017', 'Transformer\\n架构提出'),\n ('2022', 'ChatGPT发布\\n生成式AI元年'),\n ('2025', '多模态Agent\\n企业全面渗透'),\n ],\n 'source': '资料来源:斯坦福AI指数报告 2025',\n },\n },\n\n # SLIDE 5 — 市场规模\n {\n 'type': 'big_number',\n 'data': {\n 'title': '全球AI市场正以年均37%的速度增长,2026年将突破5000亿美元',\n 'number': '5,000',\n 'unit': '亿美元',\n 'description': '2026年全球人工智能市场规模预测',\n 'detail_items': [\n '中国AI市场规模约占全球15%,达750亿美元',\n '企业级AI支出中,生成式AI占比从2023年的8%升至2026年的35%',\n '每1美元AI投入平均带来3.5美元的运营效率提升',\n ],\n 'source': '资料来源:IDC全球AI市场追踪报告 2025Q4',\n },\n },\n\n # SLIDE 6 — AI技术栈\n {\n 'type': 'process_chevron',\n 'data': {\n 'title': '企业AI技术栈:从数据基座到业务价值的四层架构',\n 'steps': [\n ('数据层', '数据基础设施', '数据采集、清洗、标注\\n数据湖/数据仓库建设'),\n ('模型层', '算法与训练', '预训练大模型\\n微调与RLHF对齐'),\n ('平台层', 'MLOps平台', 'MLOps部署流水线\\n模型监控与迭代'),\n ('应用层', '业务交付', '业务场景集成\\n用户体验交付'),\n ],\n 'source': '资料来源:Gartner AI技术成熟度曲线 2025',\n },\n },\n\n # SLIDE 7 — 六大核心技术\n {\n 'type': 'icon_grid',\n 'data': {\n 'title': '六大核心AI技术正在重塑企业运营的每一个环节',\n 'items': [\n ('NLP', '自然语言处理', NAVY),\n ('CV', '计算机视觉', ACCENT_BLUE),\n ('RL', '强化学习', ACCENT_GREEN),\n ('KG', '知识图谱', ACCENT_ORANGE),\n ('GA', '生成式AI', ACCENT_RED),\n ('EA', '边缘AI', DARK_GRAY),\n ],\n 'cols': 3,\n 'source': '资料来源:团队分析',\n },\n },\n\n # ═══════════════════════════════════════════════════════\n # CHAPTER 2: 七大企业应用领域\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'section_divider',\n 'data': {\n 'section_label': '02',\n 'title': '七大企业应用领域',\n 'subtitle': '从客户体验到运营效率的全链路AI赋能',\n },\n },\n\n # SLIDE 9 — 四大核心领域\n {\n 'type': 'four_column',\n 'data': {\n 'title': '四大高价值AI应用领域已进入规模化落地阶段',\n 'items': [\n ('01', '智能客服与营销',\n '• 意图识别准确率 >95%\\n• 7×24自动应答\\n• 个性化推荐引擎\\n• 客户流失预警\\n\\n市场渗透率: 62%'),\n ('02', '供应链智能优化',\n '• 需求预测误差 \u003c8%\\n• 智能补货决策\\n• 物流路径优化\\n• 供应商风险预警\\n\\n市场渗透率: 45%'),\n ('03', '智能制造与质检',\n '• 视觉缺陷检测\\n• 预测性维护\\n• 数字孪生仿真\\n• 产线参数优化\\n\\n市场渗透率: 38%'),\n ('04', '金融风控与合规',\n '• 反欺诈实时检测\\n• 信用评分模型\\n• 反洗钱监控\\n• 智能合规审查\\n\\n市场渗透率: 71%'),\n ],\n 'source': '资料来源:麦肯锡全球AI调研 2025; 市场渗透率为大型企业数据',\n },\n },\n\n # SLIDE 10 — 案例\n {\n 'type': 'case_study',\n 'data': {\n 'title': '案例:某头部零售企业通过AI智能客服实现服务效率跃升',\n 'sections': [\n ('S', '背景', '该企业拥有3000万活跃用户,日均客服咨询量超过15万条,\\n人工客服团队120人,平均响应时间8分钟,客户满意度仅72%'),\n ('A', '方案', '部署基于大语言模型的智能客服系统:\\n• 训练意图识别模型覆盖TOP 200高频问题\\n• 构建RAG知识库整合产品文档和FAQ\\n• 多渠道统一接入(APP/微信/网页/电话)'),\n ('R', '实施', '第1-2月:数据准备与模型训练\\n第3-4月:灰度上线,AI处理30%咨询量\\n第5-6月:全量上线,AI处理75%咨询量'),\n ],\n 'result_box': ('成果', '响应时间从8分钟降至28秒 | 客户满意度从72%升至91% | 年节省人力成本1200万元'),\n 'source': '资料来源:客户授权脱敏案例',\n },\n },\n\n # SLIDE 11 — 行业AI投资排名\n {\n 'type': 'horizontal_bar',\n 'data': {\n 'title': '金融和医疗行业在AI投资规模上领先,制造业增速最快',\n 'items': [\n ('金融服务', 92, NAVY),\n ('医疗健康', 78, ACCENT_BLUE),\n ('零售电商', 71, ACCENT_GREEN),\n ('智能制造', 65, ACCENT_ORANGE),\n ('交通物流', 54, NAVY),\n ('能源化工', 42, ACCENT_BLUE),\n ('教育培训', 31, ACCENT_GREEN),\n ('农业科技', 18, ACCENT_ORANGE),\n ],\n 'summary': ('单位', '亿美元(2025年全球企业AI投资额)'),\n 'source': '资料来源:IDC全球AI支出指南 2025H2',\n },\n },\n\n # SLIDE 12 — AI应用分布\n {\n 'type': 'donut',\n 'data': {\n 'title': '自然语言处理是当前企业AI应用中占比最高的技术方向',\n 'segments': [\n (0.35, NAVY, 'NLP/大模型 35%'),\n (0.22, ACCENT_BLUE, '计算机视觉 22%'),\n (0.18, ACCENT_GREEN, '预测分析 18%'),\n (0.13, ACCENT_ORANGE, '推荐系统 13%'),\n (0.12, ACCENT_RED, '其他 12%'),\n ],\n 'center_label': '100%',\n 'center_sub': '企业AI应用',\n 'summary': 'NLP占比从2022年的22%升至2025年的35%,主要受大模型驱动',\n 'source': '资料来源:Gartner企业AI应用调研 2025',\n },\n },\n\n # SLIDE 13 — 七大领域对比表\n {\n 'type': 'data_table',\n 'data': {\n 'title': '七大AI应用领域的成熟度、投资回报与实施难度对比',\n 'headers': ['应用领域', '技术成熟度', '平均ROI', '实施周期', '数据要求', '推荐优先级'],\n 'rows': [\n ['智能客服', '★★★★★', '3-5x', '3-6个月', '中等', '⬤ 最高'],\n ['供应链优化', '★★★★☆', '2-4x', '6-12个月', '高', '⬤ 高'],\n ['智能制造', '★★★★☆', '2-3x', '6-12个月', '高', '⬤ 高'],\n ['金融风控', '★★★★★', '4-8x', '3-6个月', '很高', '⬤ 最高'],\n ['医疗AI', '★★★☆☆', '2-5x', '12-24个月', '很高', '⬤ 中'],\n ['自动驾驶', '★★★☆☆', '长期', '24-36个月', '极高', '⬤ 低'],\n ['办公自动化', '★★★★★', '2-3x', '1-3个月', '低', '⬤ 最高'],\n ],\n 'source': '资料来源:团队分析;ROI为3年期累计投资回报倍数',\n },\n },\n\n # ═══════════════════════════════════════════════════════\n # CHAPTER 3: 大语言模型与NLP\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'section_divider',\n 'data': {\n 'section_label': '03',\n 'title': '大语言模型与自然语言处理',\n 'subtitle': 'GenAI正在重新定义企业与信息交互的方式',\n },\n },\n\n # SLIDE 15 — LLM能力金字塔(3层,避免楼梯超出画面)\n {\n 'type': 'pyramid',\n 'data': {\n 'title': '大语言模型的企业应用能力呈金字塔分布,底层能力最成熟',\n 'levels': [\n ('自主Agent与复杂推理', '多步骤推理、自主决策、端到端流程执行', ''),\n ('内容生成与知识问答', '报告撰写、代码编写、RAG检索、文档摘要', ''),\n ('文本理解与基础NLP', '分类、情感分析、意图识别、实体提取', ''),\n ],\n 'source': '资料来源:团队分析;上层能力依赖下层成熟度',\n },\n },\n\n # SLIDE 16 — 商业模型 vs 开源模型\n {\n 'type': 'two_column_text',\n 'data': {\n 'title': '企业LLM选型:商业闭源模型 vs 开源模型各有适用场景',\n 'columns': [\n ('A', '商业闭源模型',\n '• GPT-4o / Claude 3.5 / Gemini\\n'\n '• 能力天花板最高,持续迭代\\n'\n '• API调用简单,无需自建基础设施\\n'\n '• 数据出境与隐私合规需重点评估\\n'\n '• 成本随调用量线性增长\\n\\n'\n '适用:快速验证、非敏感数据场景'),\n ('B', '开源模型',\n '• LLaMA 3 / Qwen 2.5 / DeepSeek\\n'\n '• 可本地部署,数据完全可控\\n'\n '• 支持深度定制和领域微调\\n'\n '• 需要GPU算力和运维团队\\n'\n '• 一次投入,边际成本极低\\n\\n'\n '适用:敏感数据、高频调用、深度定制'),\n ],\n 'source': '资料来源:团队分析(2025年3月模型能力快照)',\n },\n },\n\n # SLIDE 17 — 企业LLM场景Top 5\n {\n 'type': 'executive_summary',\n 'data': {\n 'title': '企业大模型应用场景正在从\"尝鲜\"走向\"规模化部署\"',\n 'headline': '五大已验证的企业LLM高价值场景',\n 'items': [\n ('01', '智能知识库与员工助手',\n 'RAG架构 + 企业文档 → 员工可自然语言检索内部知识,效率提升40%'),\n ('02', '代码生成与开发提效',\n 'Copilot类工具使开发效率提升30-55%,代码审查时间减半'),\n ('03', '客户服务自动化',\n '大模型驱动的客服可处理75%以上常见问题,CSAT提升15-25%'),\n ('04', '合同与合规审查',\n '自动识别合同风险条款,审查速度提升10x,漏检率降低90%'),\n ('05', '商业分析与报告生成',\n '自然语言查询数据库,自动生成分析报告,决策周期缩短60%'),\n ],\n 'source': '资料来源:麦肯锡GenAI价值评估框架 2025',\n },\n },\n\n # SLIDE 18 — 大模型落地六步法\n {\n 'type': 'vertical_steps',\n 'data': {\n 'title': '企业大模型落地的六个关键步骤',\n 'steps': [\n ('01', '场景识别与优先级排序',\n '盘点50+潜在场景 → 按ROI/可行性矩阵筛选Top 5'),\n ('02', '数据资产盘点与治理',\n '评估数据质量、覆盖度和合规性 → 建立数据准备流水线'),\n ('03', '模型选型与PoC验证',\n '商业API vs 开源部署 → 2-4周快速验证核心假设'),\n ('04', 'RAG架构与知识库构建',\n '企业文档向量化 → 检索增强生成 → 准确率达标'),\n ('05', '工程化部署与集成',\n 'MLOps流水线 → 系统对接 → 灰度上线 → 全量发布'),\n ('06', '持续监控与迭代优化',\n '建立模型漂移监控 → A/B测试 → 周期性微调更新'),\n ],\n 'source': '资料来源:团队最佳实践总结',\n },\n },\n\n # ═══════════════════════════════════════════════════════\n # CHAPTER 4: 计算机视觉与智能制造\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'section_divider',\n 'data': {\n 'section_label': '04',\n 'title': '计算机视觉与智能制造',\n 'subtitle': '感知智能驱动的产业升级',\n },\n },\n\n # SLIDE 20 — CV三大指标\n {\n 'type': 'three_stat',\n 'data': {\n 'title': '计算机视觉在工业领域已达到人类专家水平甚至超越',\n 'stats': [\n ('99.7%', '缺陷检测准确率', '半导体晶圆检测场景'),\n ('\u003c 50ms', '单帧推理延迟', '产线实时质检要求'),\n ('85%', '人工质检替代率', '3C电子行业平均水平'),\n ],\n 'source': '资料来源:中国信通院工业AI白皮书 2025',\n },\n },\n\n # SLIDE 21 — 工业质检案例\n {\n 'type': 'content_right_image',\n 'data': {\n 'title': '案例:某半导体企业部署AI视觉质检,良率提升2.3个百分点',\n 'subtitle': '基于深度学习的晶圆缺陷检测系统',\n 'bullets': [\n '部署YOLOv8模型识别12类晶圆缺陷',\n '训练数据:50万张标注图像',\n '检测速度:每片晶圆0.8秒(vs人工3分钟)',\n '漏检率从1.2%降至0.08%',\n '年节省报废成本约4500万元',\n ],\n 'takeaway': '投资回收期仅8个月,第二年起年净收益超3000万元',\n 'image_label': 'AI质检系统架构图',\n 'source': '资料来源:客户授权脱敏案例',\n },\n },\n\n # SLIDE 22 — 自动驾驶市场份额\n {\n 'type': 'stacked_bar',\n 'data': {\n 'title': '全球自动驾驶市场快速增长,中国市场份额持续提升',\n 'periods': ['2022', '2023', '2024', '2025E', '2026E'],\n 'series': [\n ('北美', NAVY),\n ('中国', ACCENT_BLUE),\n ('欧洲', ACCENT_GREEN),\n ('其他', ACCENT_ORANGE),\n ],\n 'data': [\n [45, 28, 18, 9],\n [42, 31, 18, 9],\n [39, 33, 18, 10],\n [36, 35, 17, 12],\n [35, 37, 17, 11],\n ],\n 'summary': ('趋势', '2025-2026为预测值,中国市场份额CAGR达36%'),\n 'source': '资料来源:IHS Markit自动驾驶市场追踪 2025',\n },\n },\n\n # ═══════════════════════════════════════════════════════\n # CHAPTER 5: AI实施框架\n # ═══════════════════════════════════════════════════════\n {\n 'type': 'section_divider',\n 'data': {\n 'section_label': '05',\n 'title': 'AI实施框架与行动计划',\n 'subtitle': '从战略规划到90天落地的完整路径',\n },\n },\n\n # SLIDE 24 — AI项目五阶段\n {\n 'type': 'process_chevron',\n 'data': {\n 'title': 'AI项目实施遵循五阶段方法论,每个阶段都有明确交付物',\n 'steps': [\n ('1', '评估与设计(5周)', '业务痛点梳理\\n数据资产盘点\\n技术架构设计'),\n ('2', 'PoC验证(4周)', '快速原型开发\\n核心假设验证\\n用户反馈收集'),\n ('3', '规模部署(8周)', '工程化开发\\n系统集成测试\\n灰度→全量上线'),\n ('4', '持续运营(长期)', '效果监控与迭代\\n模型更新\\n场景扩展'),\n ],\n 'source': '资料来源:团队AI项目交付方法论',\n },\n },\n\n # SLIDE 25 — 风险矩阵\n {\n 'type': 'risk_matrix',\n 'data': {\n 'title': 'AI项目实施面临的核心风险及应对策略',\n 'grid_colors': [\n [ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED],\n [ACCENT_GREEN, ACCENT_GREEN, ACCENT_ORANGE],\n [ACCENT_GREEN, ACCENT_GREEN, ACCENT_GREEN],\n ],\n 'grid_lights': [\n [LIGHT_GREEN, LIGHT_ORANGE, LIGHT_RED],\n [LIGHT_GREEN, LIGHT_GREEN, LIGHT_ORANGE],\n [LIGHT_GREEN, LIGHT_GREEN, LIGHT_GREEN],\n ],\n 'risks': [\n (0, 2, '数据质量不足'),\n (0, 1, '模型幻觉风险'),\n (1, 1, '组织变革阻力'),\n (1, 2, '合规与伦理'),\n (2, 1, '人才短缺'),\n ],\n 'y_labels': ['高', '中', '低'],\n 'x_labels': ['低', '中', '高'],\n 'notes': '影响程度 →',\n 'source': '资料来源:团队风险评估框架',\n },\n },\n\n # SLIDE 26 — 利益相关方矩阵\n {\n 'type': 'stakeholder_map',\n 'data': {\n 'title': '成功的AI项目需要跨部门协同,关键在于获得CEO和CTO的双重支持',\n 'quadrants': [\n ('高关注 高影响', 'Key Players', LIGHT_BLUE, ['CEO/董事会', 'CTO/CDO']),\n ('高关注 低影响', 'Keep Informed', LIGHT_GREEN, ['IT运维团队', '合规/法务']),\n ('低关注 高影响', 'Keep Satisfied', LIGHT_ORANGE, ['业务部门负责人', '外部供应商']),\n ('低关注 低影响', 'Monitor', BG_GRAY, ['一线员工']),\n ],\n 'x_label': '影响力 →',\n 'y_label': '关注度 ↑',\n 'summary': '右上象限为关键决策者,需重点沟通和争取支持',\n 'source': '资料来源:团队利益相关方分析框架',\n },\n },\n\n # SLIDE 27 — 90天行动计划\n {\n 'type': 'action_items',\n 'data': {\n 'title': 'AI落地90天行动计划:三个阶段、十项关键任务',\n 'actions': [\n ('组建AI核心团队(5-7人)', '第1-2周', '招募AI架构师和数据工程师', 'CEO/CTO'),\n ('完成业务痛点诊断与场景清单', '第2-4周', '访谈各部门负责人,梳理50+场景', 'CDO'),\n ('数据资产盘点与质量评估', '第3-6周', '评估数据覆盖度、质量和合规性', '数据团队'),\n ('选定Top 3场景并完成技术选型', '第4-6周', '按ROI/可行性矩阵排序', 'AI架构师'),\n ('启动第一个PoC项目', '第6-10周', '4周快速验证核心假设', '项目经理'),\n ('建立AI治理与合规框架', '第4-8周', '数据隐私、模型可解释性规范', '法务/合规'),\n ('PoC效果评估与管理层汇报', '第10-12周', '量化ROI,争取规模化预算', 'CDO'),\n ('制定规模化部署计划与预算', '第12周', '明确H2投入计划和里程碑', 'CFO/CTO'),\n ],\n 'source': '资料来源:团队AI落地最佳实践',\n },\n },\n\n # SLIDE 28 — SWOT\n {\n 'type': 'swot',\n 'data': {\n 'title': '典型中大型企业AI能力SWOT分析',\n 'quadrants': [\n ('优势', ACCENT_BLUE, LIGHT_BLUE,\n '• 丰富的业务数据积累\\n• 清晰的业务场景和KPI\\n• 成熟的IT基础设施\\n• 管理层战略重视'),\n ('劣势', ACCENT_ORANGE, LIGHT_ORANGE,\n '• AI人才储备不足\\n• 数据孤岛和质量问题\\n• 缺乏MLOps工程能力\\n• 组织变革推进缓慢'),\n ('机会', ACCENT_GREEN, LIGHT_GREEN,\n '• 大模型大幅降低AI门槛\\n• 云服务商提供全栈AI平台\\n• 行业数字化转型窗口期\\n• 政策支持与资金补贴'),\n ('威胁', ACCENT_RED, LIGHT_RED,\n '• 竞争对手AI领先优势\\n• 数据安全与合规趋严\\n• AI人才争夺白热化\\n• 技术迭代速度极快'),\n ],\n 'source': '资料来源:团队诊断框架',\n },\n },\n\n # SLIDE 29 — ROI瀑布图\n {\n 'type': 'waterfall',\n 'data': {\n 'title': '典型企业AI项目三年期投资回报模型:净收益达初始投资的3.5倍',\n 'items': [\n ('初始投入', 500, 'base'),\n ('人力节约', 420, 'up'),\n ('效率提升', 380, 'up'),\n ('收入增长', 650, 'up'),\n ('运维成本', 200, 'down'),\n ('净收益', 1750, 'base'),\n ],\n 'max_val': 2000,\n 'summary': '单位:万元(基于智能客服+供应链优化两个场景测算)',\n 'source': '资料来源:团队ROI模型测算',\n },\n },\n\n # SLIDE 30 — KPI仪表盘\n {\n 'type': 'dashboard_kpi_chart',\n 'data': {\n 'title': 'AI项目效果监控仪表盘:关键指标全部达到或超出目标',\n 'kpi_cards': [\n ('78%', '客服自动化率', '↑23%', ACCENT_GREEN),\n ('94.2%', '预测准确率', '↑8.5%', ACCENT_GREEN),\n ('¥1,200万', '人力成本节约', '超目标15%', ACCENT_BLUE),\n ('91分', '客户满意度', '↑19分', ACCENT_GREEN),\n ],\n 'chart_data': {\n 'labels': ['M1', 'M2', 'M3', 'M4', 'M5', 'M6'],\n 'actual': [120, 250, 350, 420, 470, 500],\n 'target': [0, 30, 120, 280, 480, 720],\n 'legend': [('累计投入', NAVY), ('累计收益', ACCENT_GREEN)],\n },\n 'summary': '项目在第5个月实现收支平衡,第6个月累计净收益220万元',\n 'source': '资料来源:项目管理办公室月度报告',\n },\n },\n\n # SLIDE 31 — 核心结论\n {\n 'type': 'key_takeaway',\n 'data': {\n 'title': '核心结论:AI不再是\"要不要做\"的问题,而是\"怎么做好\"的问题',\n 'left_text': (\n '全球AI市场以37% CAGR增长,2026年达5000亿美元。'\n '金融、零售、制造三大行业AI渗透率已超过40%。'\n '大语言模型将企业AI的应用门槛降低了一个数量级——'\n '过去需要机器学习团队从零搭建的能力,'\n '现在通过API调用或开源模型微调即可实现。\\n\\n'\n '然而,80%的AI项目仍然停留在PoC阶段无法规模化。'\n '核心瓶颈不在技术,而在数据治理、组织协同和持续运营。'\n ),\n 'takeaways': (\n '• 立即启动:选择1-2个高ROI场景(如智能客服、办公自动化),90天内完成PoC\\n'\n '• 构建基座:投资数据治理和MLOps平台,为规模化打下基础\\n'\n '• 人才先行:组建5-7人AI核心团队,兼顾技术能力和业务理解\\n'\n '• 治理并行:建立AI伦理和合规框架,避免\"先污染后治理\"'\n ),\n 'source': '资料来源:团队综合分析',\n },\n },\n\n # SLIDE 32 — 实施前后对比\n {\n 'type': 'metric_comparison',\n 'data': {\n 'title': 'AI实施前后关键业务指标显著改善',\n 'metrics': [\n ('客服响应时间', '8分钟', '28秒', '↓ 94%'),\n ('需求预测准确率', '72%', '94%', '↑ 22pp'),\n ('产品缺陷率', '1.8%', '0.3%', '↓ 83%'),\n ('合同审查周期', '5天', '4小时', '↓ 97%'),\n ('员工搜索信息时间', '45分钟/天', '8分钟/天', '↓ 82%'),\n ],\n 'source': '资料来源:客户案例汇总(多行业平均值)',\n },\n },\n\n # SLIDE 33 — Closing\n {\n 'type': 'closing',\n 'data': {\n 'title': '谢谢',\n 'message': '人工智能不会取代企业,但善用AI的企业将取代不用AI的企业。\\n\\n如有任何关于AI战略规划的问题,欢迎联系我们的团队。',\n },\n },\n]","content_type":"text/x-python; charset=utf-8","language":"python","size":28807,"content_sha256":"5881ecffe44eaa2989164e7f3a259fc2cbe87b399904eb03d39a2c36bd4ac130"},{"filename":"NOTICE","content":"Mck-ppt-design-skill — McKinsey PPT Design Framework\nCopyright 2024-2026 Kaku Li (https://github.com/likaku)\n\nThis product includes software developed by Kaku Li.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n========================================================================\n\nNOTICE: This file is required by Section 4(d) of the Apache License 2.0.\nAny redistribution of this software — in source or binary form, modified\nor unmodified — MUST include this NOTICE file in its entirety. Failure\nto include this file constitutes a violation of the license terms.\n\n========================================================================\n\nThird-party components:\n\n- python-pptx (MIT License) — https://github.com/scanny/python-pptx\n- lxml (BSD License) — https://lxml.de/\n- Pillow (HPND License) — https://python-pillow.org/\n- rembg (MIT License) — https://github.com/danielgatis/rembg\n\nAll trademarks referenced herein are the property of their respective\nowners and are used solely for identification purposes.\n","content_type":"text/plain; charset=utf-8","language":null,"size":1510,"content_sha256":"eb9636ea5c057a04be7ba609803e04e7c91f70c454b128839eb1c01f720caa33"},{"filename":"README.md","content":"\u003cdiv align=\"center\">\n\n# MCK PPT Design Skill\n\n**AI-native PowerPoint design system — 67 layouts · Harness Engineering · BLOCK_ARC charts · QA pipeline · Python runtime**\n\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n[![Python](https://img.shields.io/badge/Python-3.8+-3776AB.svg?logo=python&logoColor=white)](https://python.org)\n[![python-pptx](https://img.shields.io/badge/python--pptx-0.6.21+-orange.svg)](https://python-pptx.readthedocs.io)\n[![GitHub stars](https://img.shields.io/github/stars/likaku/Mck-ppt-design-skill?style=social)](https://github.com/likaku/Mck-ppt-design-skill)\n\n> **Copyright © 2024-2026 Kaku Li.** Licensed under [Apache 2.0](LICENSE). See [NOTICE](NOTICE) for details.\n\n[English](#-quick-start) · [中文说明](#中文说明) · [Harness Guide](#-harness-engineering-mode)\n\n\u003c/div>\n\n---\n\n## 🖼️ Sample Output\n\n| Cover Page | Strategy Analysis | Data Dashboard |\n|:---:|:---:|:---:|\n| \u003cimg width=\"420\" alt=\"Cover\" src=\"https://github.com/user-attachments/assets/075ec46d-dd73-4454-92d0-84184b78d276\" /> | \u003cimg width=\"420\" alt=\"Content\" src=\"https://github.com/user-attachments/assets/3b25f071-8a81-48e3-a62b-9d9be9026f2e\" /> | \u003cimg width=\"420\" alt=\"Table\" src=\"https://github.com/user-attachments/assets/be327c14-aff9-459f-89b0-d4a8bffaabfc\" /> |\n| **4-Column Framework** | **Color System** | **Executive Summary** |\n| \u003cimg width=\"420\" alt=\"4-Column\" src=\"https://github.com/user-attachments/assets/687cee47-13bb-4d6b-840f-77f8e001a62b\" /> | \u003cimg width=\"420\" alt=\"Colors\" src=\"https://github.com/user-attachments/assets/41371c47-608f-4857-9bfe-791121ec1579\" /> | \u003cimg width=\"420\" alt=\"Summary\" src=\"https://github.com/user-attachments/assets/c5b6e52a-fd91-4c28-88a4-82fdfedfd956\" /> |\n\n---\n\n## ⚡ Quick Start\n\n```bash\npip install python-pptx lxml\n```\n\n```python\nimport sys, os\nsys.path.insert(0, os.path.expanduser('~/.workbuddy/skills/mck-ppt-design'))\nfrom mck_ppt import MckEngine\nfrom mck_ppt.constants import *\n\neng = MckEngine(total_slides=12)\neng.cover(title='Q1 2026 Strategy Review', subtitle='Board Presentation', date='2026')\neng.toc(items=[('1', 'Market Overview', 'Current landscape'), ('2', 'Strategy', 'Key actions')])\neng.table_insight(title='Three shifts driving market restructuring',\n headers=['Dimension', 'Before', 'After'],\n rows=[['Distribution', 'Offline-first', 'Digital-first'],\n ['Pricing', 'Cost-plus', 'Value-based']],\n insights=['Digital channels now control 60% of CAC', 'Value pricing unlocks 3× margins'])\neng.donut(title='Revenue Mix', segments=[(0.45, NAVY, 'Product'), (0.35, ACCENT_BLUE, 'Service'), (0.20, ACCENT_GREEN, 'Other')])\neng.timeline(title='12-month roadmap', milestones=[('Q1', 'Foundation'), ('Q2', 'Pilot'), ('Q3', 'Scale'), ('Q4', 'Review')])\neng.closing(title='Thank You')\neng.save('output/deck.pptx')\n```\n\n### AI Agent Compatibility\n\n| AI Agent | Status |\n|----------|--------|\n| **WorkBuddy / Codebuddy** | ✅ Native skill (`mck-ppt-design`) |\n| **Claude / Claude Code** | ✅ Load SKILL.md as skill |\n| **Cursor / Continue** | ✅ Add as project rule |\n| **Any LLM** | ✅ Feed SKILL.md as context |\n\n---\n\n## 🔱 Harness Engineering Mode\n\n> **New in v2.3.3-harness** — Transforms the skill from vibe-coding into structured 5-stage generation with machine-readable gates.\n\n### The Problem: Vibe Coding vs. Harness\n\nWithout Harness, the entire 3,967-line SKILL.md was loaded every time, AI jumped straight to code, and errors were only discovered *after* rendering. With Harness, context is loaded progressively, structure is locked before content, and gates run as Python scripts — not AI self-evaluation.\n\n| Dimension | Without Harness | With Harness |\n|-----------|-----------------|--------------|\n| Context load | 3,967 lines every time (~6k tokens) | 245-line entry → load per-stage (~800 tokens) |\n| Structure design | AI guesses layouts directly | `outline.json` locks every slide's layout + insight |\n| Action titles | Topic labels (\"Market Overview\") | Full insight sentences (\"Three shifts driving restructuring\") |\n| Error discovery | After rendering (expensive to fix) | S3 gate catches API format errors before render |\n| Gate enforcement | AI self-declaration (\"looks good\") | `gate_check.py` outputs `gate_result.json` — `passed` is a Python bool |\n| Experience accumulation | Discarded after fix | `experiences/*.md` → AI reads on next deck, avoids repeat mistakes |\n| QA score (A/B test) | 95/100 (simple layouts, fewer QA rules triggered) | 92/100 (complex layouts, richer content) |\n| Insight titles (A/B test) | **0/7** content slides | **8/12** content slides |\n\n> **Note on QA scores**: The no-Harness deck scored higher *because* it used simpler layouts (fewer QA rules triggered). The Harness deck used richer layouts (donut, matrix_2×2, value_chain) that expose more QA checks. QA score measures *layout health*, not *content quality* — they are different dimensions.\n\n### 5-Stage Generation Flow\n\n```\n┌─────────┐ ┌────────────────┐ ┌───────────────────┐ ┌──────────────────┐ ┌──────────┐\n│ S1 │──▶│ S2 ⭐ │──▶│ S3 ⭐ │──▶│ S4 ⭐⭐ │──▶│ S5 │\n│ Brief │ │ Structure │ │ Content │ │ Render + QA │ │ Deliver │\n│ brief.md│ │ outline.json │ │ content.json │ │ .pptx │ │ + Learn │\n└─────────┘ └────────────────┘ └───────────────────┘ └──────────────────┘ └──────────┘\n ⭐ = gate (FAIL → fix in current stage, never skip)\n S3/S4 gates run Python scripts → read JSON result → no AI self-eval\n```\n\n| Stage | Input | Output | Gate |\n|-------|-------|--------|------|\n| S1 Brief | User request | `brief.md` (audience, goal, duration, messages) | AI self-check: 3 fields non-empty |\n| S2 Structure | `brief.md` | `outline.json` (layout + insight per slide) | AI self-check: cover exists, layouts valid, titles are sentences |\n| S3 Content | `outline.json` | `content.json` (copy, data, sources) | **`gate_check_s3.py`** → `gate_s3.json` |\n| S4 Render+QA | `content.json` | `.pptx` + `gate_result.json` | **`gate_check.py`** → `gate_result.json` |\n| S5 Deliver | `gate_result.json` (`passed: true`) | Final `.pptx` + `experiences/` update | Read `gate_result.json` before declaring done |\n\n### Machine-Readable Gates (the key innovation)\n\n**S3 Gate — Content audit before rendering:**\n\n```bash\npython ~/.workbuddy/skills/mck-ppt-design/references/scripts/gate_check_s3.py \\\n ./ppt-project-foo/content.json ./ppt-project-foo/\n```\n\nChecks (all automatic, no AI judgment):\n- `four_column` / `executive_summary` / `meet_the_team` items are 3-tuples `(num, title, desc)`\n- `matrix_2x2` quadrants are 3-tuples `(label, bg_color, desc)` — exactly 4\n- `process_chevron` steps ≤ 5, labels contain no `\\n`, desc ≤ 50 chars\n- `donut` / `pie` segments ≤ 6\n- `grouped_bar` categories ≤ 6, series ≤ 3\n- Every content slide has a non-empty `source`\n- Action titles are full sentences (> 10 chars)\n\nOutput `gate_s3.json`:\n```json\n{\n \"passed\": true,\n \"verdict\": \"PASS — 可进入 S4 渲染\",\n \"fail_items\": [],\n \"pass_items\": [...]\n}\n```\n\n**S4 Gate — QA after rendering:**\n\n```bash\npython ~/.workbuddy/skills/mck-ppt-design/references/scripts/gate_check.py \\\n ./output.pptx ./ppt-project-foo/\n```\n\nClassifies every QA error as `user_code` (must fix) or `engine_bug` (whitelisted, with written evidence). `passed = user_code_errors == 0`. AI cannot override this.\n\nOutput `gate_result.json`:\n```json\n{\n \"passed\": true,\n \"overall_score\": 92,\n \"checklist\": {\n \"user_code_errors\": 0,\n \"engine_bug_errors\": 7,\n \"warnings\": 1\n },\n \"verdict\": \"PASS — 可进入 S5 交付\",\n \"user_code_error_detail\": []\n}\n```\n\n### Anti-Patterns (from `SKILL.md` — read before every generation)\n\nThe three most common failure modes observed in real execution:\n\n1. **\"Gate passed\" without running the script** — AI declares pass based on self-assessment. Fix: read `gate_result.json`. If file doesn't exist, gate hasn't run.\n2. **S3 \"checked in my head\"** — Mental review misses API format errors every time. Fix: run `gate_check_s3.py`. Today's 3 API format bugs were all caught by script, none by self-review.\n3. **\"engine_bug\" as escape hatch** — AI reclassifies user errors as engine bugs to pass the gate. Fix: `ENGINE_BUG_WHITELIST` in `gate_check.py` is code, not AI judgment. Add to whitelist only with written evidence.\n\n### Knowledge Structure\n\n```\n~/.workbuddy/skills/mck-ppt-design/\n├── SKILL.md # Entry (~245 lines): flow + hard rules + index\n│ (was 3,967 lines before Harness)\n├── references/\n│ ├── INDEX.md # Per-stage load router\n│ ├── layout-matrix.yaml # Layout × theme × char_budget (source of truth)\n│ ├── team/\n│ │ ├── brand-guide.md # Colors, fonts, design principles\n│ │ └── presentation-convention.md\n│ ├── framework/\n│ │ ├── engine-api.md # 67-method API quick reference\n│ │ ├── guard-rails.md # 10 production rules\n│ │ └── planning-guide.md # Layout selection, content density\n│ └── scripts/\n│ ├── gate_check.py # S4 gate: QA classification → gate_result.json\n│ └── gate_check_s3.py # S3 gate: API format + count → gate_s3.json\n└── experiences/\n ├── overflow.md # Text overflow patterns (6 entries)\n ├── chart-limits.md # Chart data limits (2 entries)\n ├── layout-pitfalls.md # Layout-specific traps (5 entries)\n └── cjk-issues.md # CJK rendering (2 entries)\n```\n\n---\n\n## 🏗️ Architecture\n\n### v1.x → v2.0: GPU → CPU Shift\n\n> The fundamental change in v2.0: **moving ~80% of compute from GPU (LLM inference) to CPU (deterministic Python execution).**\n\nIn v1.x, every coordinate, every color, every spacing value was produced by the model token-by-token. A single donut chart required **2,800 `add_rect()` calls** — each one a GPU-computed token. A 30-slide deck burned 40,000–60,000 output tokens and ~2 minutes per chart.\n\nv2.0 extracts that deterministic work into Python: `eng.donut()` → 20 AI tokens → 2,745 lines of CPU execution.\n\n| | v1.x (Pure GPU) | v2.0 (GPU + CPU) |\n|---|---|---|\n| Compute split | ~100% GPU | ~20% GPU (decisions) + ~80% CPU (execution) |\n| Chart rendering | `add_rect()` stacking (100–2,800 shapes) | `BLOCK_ARC` native arcs (3–4 shapes) |\n| Output tokens / 30-slide deck | 40,000–60,000 | 9,000–12,000 |\n| Chart generation time | ~2 min | \u003c1 sec |\n| File size (chart-heavy) | 2–5 MB | 0.5–1 MB |\n\n### 5-Layer Architecture (v2.3.3-harness)\n\n```\n┌──────────────────────────────────────────────────────────┐\n│ L5 Harness Engineering (v2.3.3-harness) │\n│ 5-stage flow · 4 gates · 3-tier knowledge │\n│ machine-readable gates · self-refinement loop │\n├──────────────────────────────────────────────────────────┤\n│ L4 Post-Processing Pipeline │\n│ Three-layer XML corruption defense │\n│ Full p:style / shadow / 3D sanitization │\n│ CJK font injection (KaiTi for all East Asian text) │\n├──────────────────────────────────────────────────────────┤\n│ L3 Review + Auto-fix Pipeline (v2.3) │\n│ Dual QA (layout + narrative) → AutoFixPipeline │\n│ Peer font harmonization · Gate: 0 ERROR = PASS │\n├──────────────────────────────────────────────────────────┤\n│ L2 Python Runtime Engine (mck_ppt/) │\n│ 67 high-level layout methods │\n│ Drawing primitives · XML cleanup · Constants │\n│ BLOCK_ARC chart engine · AI cover image (v2.2) │\n├──────────────────────────────────────────────────────────┤\n│ L1 Design Specification │\n│ Color system + typography + layout patterns │\n│ (Split into references/ for progressive loading) │\n└──────────────────────────────────────────────────────────┘\n```\n\n---\n\n## 🛡️ Production Guard Rails\n\n13 rules hard-won from production use:\n\n| # | Rule | What It Prevents |\n|---|------|-----------------|\n| 1 | Never use connectors | File corruption from connector `p:style` |\n| 2 | Always call `_clean_shape()` | Shadow/3D artifacts |\n| 3 | Run `full_cleanup()` after save | Residual theme effects |\n| 4 | Set `set_ea_font()` on CJK text | Chinese characters rendering as boxes |\n| 5 | Use `add_hline()` not `add_line()` | Connector-based lines causing repair prompts |\n| 6–8 | Overflow + spacing + dynamic sizing | Overlapping text, missing content |\n| 9 | Mandatory BLOCK_ARC for circular charts | Rect-block bloat |\n| 10 | Peer font consistency | Same-row shapes with different font sizes |\n| 11 | Text-line collision check | Text overlapping separator lines |\n| 12 | Post-generation QA gate | Silent defects |\n| 13 | Chart legend overflow check | Legend exceeding content area |\n\n---\n\n## 📁 Project Structure\n\n```\n├── SKILL.md # Harness entry (~245 lines, was 3,967)\n├── mck_ppt/ # Python runtime engine\n│ ├── engine.py # 67 layout methods\n│ ├── core.py # Drawing primitives + XML cleanup\n│ ├── constants.py # Colors, typography, grid\n│ ├── qa.py # Layout QA engine\n│ ├── review.py # Review + auto-fix pipeline\n│ ├── deck_builder.py # Storyline-driven deck generator\n│ ├── cover_image.py # AI cover image (Hunyuan 2.0)\n│ └── storylines/ # Pre-built storyline templates\n├── references/\n│ ├── INDEX.md # Per-stage knowledge router\n│ ├── layout-matrix.yaml # Layout × char_budget (source of truth)\n│ ├── team/ # Brand guide, presentation conventions\n│ ├── framework/ # Engine API, guard rails, planning guide\n│ └── scripts/\n│ ├── gate_check.py # S4 QA gate → gate_result.json\n│ └── gate_check_s3.py # S3 content gate → gate_s3.json\n├── experiences/\n│ ├── overflow.md # Accumulated overflow lessons\n│ ├── chart-limits.md # Chart data limits\n│ ├── layout-pitfalls.md # Layout-specific pitfalls\n│ └── cjk-issues.md # CJK rendering issues\n├── assets/icons/ # 6 pre-built PNG icons\n├── examples/ # Minimal example + staircase demo\n└── CHANGELOG.md\n```\n\n---\n\n## 📊 Version History\n\n| Version | Date | Highlights |\n|---------|------|------------|\n| **v2.3.3-harness** | 2026-05-03 | **Harness Engineering**: SKILL.md 3,967→245 lines; 3-tier knowledge split; machine-readable S3/S4 gates (`gate_check.py`, `gate_check_s3.py`); `experiences/` self-refinement loop; anti-pattern warnings |\n| **v2.3.3** | 2026-04-09 | Layout polish, unified color palette, retired 5 legacy layouts |\n| **v2.3.2** | 2026-03-25 | DeckBuilder storyline generator; stacked_bar fix; `chart_legend_overflow` QA rule; 33-slide AI enterprise demo |\n| **v2.3.1** | 2026-03-24 | Dynamic row height for `numbered_list_panel`; `text_line_collision` QA rule |\n| **v2.3.0** | 2026-03-24 | Post-generation review + auto-fix pipeline; peer font harmonization; 14 errors → 0 |\n| **v2.2.0** | 2026-03-23 | AI cover image pipeline (Hunyuan 2.0 + rembg) |\n| **v2.0.5** | 2026-03-21 | `table_insight` (#71), icon library, unified release |\n| **v2.0** | 2026-03-19 | BLOCK_ARC charts, Python runtime engine, three-tier architecture |\n| v1.9 | 2026-03-12 | 9 production guard rails |\n| v1.0 | 2026-03-02 | Initial release |\n\n---\n\n## Community\n\n\u003ctable width=\"100%\">\n \u003ctr>\n \u003ctd align=\"center\" width=\"50%\" valign=\"top\">\n \u003cp>\u003cstrong>WeChat Group is now over 200 people, please add my wechat account to join the group: Kashinmoto\u003c/strong>\u003c/p>\n \u003c/td>\n \u003ctd align=\"center\" width=\"50%\" valign=\"top\">\n \u003cp>\u003cstrong>Discord Server\u003c/strong>\u003c/p>\n \u003cbr/>\n \u003ca href=\"https://discord.gg/SaFybFAT\">\n \u003cimg src=\"https://img.shields.io/badge/Discord-Join_Community-5865F2?style=for-the-badge&logo=discord&logoColor=white\" alt=\"Discord\" />\n \u003c/a>\n \u003cbr/>\u003cbr/>\n \u003cp>Click above to join our global community\u003c/p>\n \u003c/td>\n \u003c/tr>\n\u003c/table>\n\n---\n\n## 中文说明\n\n\u003cdetails>\n\u003csummary>\u003cb>点击展开中文文档\u003c/b>\u003c/summary>\n\n### Harness 工程化模式(v2.3.3-harness 新增)\n\n这次更新的核心不是新增版式,而是**把 AI 做 PPT 的方式从\"感觉驱动\"改成\"工程化驱动\"**。\n\n#### 主要变化\n\n**SKILL.md 从 3,967 行瘦身到 245 行**。原来所有内容全量加载(~6000 tokens),现在入口只有 245 行,每个阶段按需加载对应文件(~800 tokens 起)。\n\n**五阶段流程 + 机读化门禁**。生成一个 PPT 不再是\"一句话 → 直接写代码\",而是:\n1. S1 需求定义 → 写 `brief.md`\n2. S2 结构设计 → 写 `outline.json`(每页确定版式和洞见句)\n3. S3 内容填充 → 写 `content.json` → **运行 `gate_check_s3.py`**\n4. S4 渲染 + QA → 生成 `.pptx` → **运行 `gate_check.py`**\n5. S5 交付 → 读 `gate_result.json` 确认 `passed: true` 再交付\n\n**门禁是 Python 脚本,不是 AI 口头判断**。这是最关键的改变。之前 AI 可以说\"7个错误都是 engine 设计行为,门禁通过\"——这是 AI 在给自己写完成证书。现在 `gate_check.py` 运行后输出 `gate_result.json`,`passed` 字段由 `user_code_errors == 0` 这个 Python 布尔值决定,AI 无法绕过。\n\n#### A/B 对比(相同提示词,真实测量)\n\n| 维度 | 无 Harness | 有 Harness |\n|------|-----------|-----------|\n| 洞见标题比例 | **0/7**(全是\"主题词\"标签) | **8/12**(含数字判断的完整句子)|\n| 版式多样性 | ~5 种 | 9 种(donut/matrix_2x2/value_chain 等)|\n| 章节结构 | 10 页平铺 | 15 页三章节+分隔页 |\n| QA 分数 | 95(简单版式,触发规则少)| 92(复杂版式,更多内容) |\n\n> QA 分数高不代表质量好——无 Harness 版用了更简单的版式,触发更少 QA 规则,所以分更高。Harness 版用了更丰富的版式(donut、四象限矩阵、价值链),洞见密度高得多。\n\n#### 经验沉淀(Self-Refinement)\n\n每次修正\"模式性问题\"后,AI 会写入 `experiences/` 目录对应文件。下次做 PPT,AI 在 S3 阶段会读这些文件,主动避免同类错误:\n\n- `experiences/overflow.md` — 文字溢出经验(6 条)\n- `experiences/chart-limits.md` — 图表数据段数限制(2 条)\n- `experiences/layout-pitfalls.md` — 版式特定踩坑(5 条)\n- `experiences/cjk-issues.md` — 中文渲染问题(2 条)\n\n### v2.0 核心架构变化\n\nv2.0 的本质是**将约 80% 的算力从 GPU(大模型推理)迁移到 CPU(确定性 Python 执行)**。\n\n一个甜甜圈图从 2,800 个 `add_rect()` 调用变成了 1 行 `eng.donut()`,从 ~2 分钟 GPU 推理变成 \u003c1 秒 CPU 执行,文件从 5MB 缩小到 0.5MB。\n\n```python\n# v1.x:AI 输出每个方块的坐标(2800+ tokens)\nfor angle in range(0, 360):\n add_rect(slide, x + cos(angle)*r, y + sin(angle)*r, ...)\n\n# v2.0:AI 输出 20 个 token → CPU 执行 2745 行确定性代码\neng.donut(title='市场份额', segments=[(0.35, NAVY, '我们'), ...])\n```\n\n### 快速上手\n\n```bash\npip install python-pptx lxml\n# 安装到 WorkBuddy / Codebuddy:在 skill 市场搜索 mck-ppt-design\n# 或手动:cp -r /path/to/mck-ppt-design ~/.workbuddy/skills/\n```\n\n\u003c/details>\n\n---\n\n\u003cdiv align=\"center\">\n\u003csub>Apache 2.0 · © 2024-2026 \u003cstrong>Kaku Li\u003c/strong> · \u003ca href=\"https://github.com/likaku/Mck-ppt-design-skill\">GitHub\u003c/a> · \u003ca href=\"https://github.com/likaku/Mck-ppt-design-skill/issues\">Issues\u003c/a>\u003c/sub>\n\u003c/div>\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":21362,"content_sha256":"f2058627f43aaccb54f96391e27f5f0a7847e04f770f3598ca316829ad754692"},{"filename":"references/color-palette.md","content":"# Color Palette Quick Reference\n\n| Name | Hex | RGB | Usage |\n|------|-----|-----|-------|\n| NAVY | #051C2C | (5, 28, 44) | Primary — titles, circles, TOC highlights |\n| BLACK | #000000 | (0, 0, 0) | Separator lines, title underlines |\n| WHITE | #FFFFFF | (255, 255, 255) | Backgrounds, text on navy |\n| DARK_GRAY | #333333 | (51, 51, 51) | Body text |\n| MED_GRAY | #666666 | (102, 102, 102) | Secondary text, labels, source notes |\n| LINE_GRAY | #CCCCCC | (204, 204, 204) | Table row separators |\n| BG_GRAY | #F2F2F2 | (242, 242, 242) | Background panels, takeaway areas |\n\n## Python Constants\n\n```python\nNAVY = RGBColor(0x05, 0x1C, 0x2C)\nBLACK = RGBColor(0x00, 0x00, 0x00)\nWHITE = RGBColor(0xFF, 0xFF, 0xFF)\nDARK_GRAY = RGBColor(0x33, 0x33, 0x33)\nMED_GRAY = RGBColor(0x66, 0x66, 0x66)\nLINE_GRAY = RGBColor(0xCC, 0xCC, 0xCC)\nBG_GRAY = RGBColor(0xF2, 0xF2, 0xF2)\n```\n\n## Font Size Hierarchy\n\n| Size | Usage |\n|------|-------|\n| 44pt | Cover title only |\n| 28pt | Section header |\n| 22pt | Action title (bold, Georgia) |\n| 18pt | Sub-header |\n| 14pt | Body text (primary) |\n| 9pt | Footnote / source |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":1120,"content_sha256":"f122c253a769a6d77459703dd47f3e5bdcfc53359ab3eaa2940dc461ba96e525"},{"filename":"references/framework/engine-api.md","content":"# Engine API Reference — MckEngine\n\n> 框架级规范。67个高级方法速查表,每个方法对应一张幻灯片。\n> 详细代码模板见 `references/layouts/` 对应文件。\n\n## Setup\n\n```python\nimport sys, os\nsys.path.insert(0, os.path.expanduser('~/.workbuddy/skills/mck-ppt-design'))\nfrom mck_ppt import MckEngine\nfrom mck_ppt.constants import * # NAVY, ACCENT_BLUE, BG_GRAY, etc.\n\neng = MckEngine(total_slides=N) # N = 计划总页数(用于页码)\neng.save('output/deck.pptx') # 自动 full_cleanup,无需手动清理\n```\n\n## Dependencies\n\n```bash\npip install python-pptx lxml\n# 封面图生成额外需要:\npip install tencentcloud-sdk-python rembg pillow numpy\n```\n\n---\n\n## Methods by Category\n\n### A. Structure(结构导航)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.cover(title, subtitle='', author='', date='', cover_image=None)` | #1 | 封面页。cover_image: None/`'auto'`/文件路径 |\n| `eng.toc(title='目录', items=None, source='')` | #6 | 目录页。items: [(num, title, desc)] |\n| `eng.section_divider(section_label, title, subtitle='')` | #5 | 章节分隔页 |\n| `eng.appendix_title(title, subtitle='')` | #7 | 附录标题页 |\n| `eng.closing(title, message='', source_text='')` | #36 | 结束页 |\n\n### B. Data & Stats(数据统计)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.big_number(title, number, unit='', description='', detail_items=None, source='', bottom_bar=None)` | #8 | 大数字页 |\n| `eng.two_stat(title, stats, detail_items=None, source='')` | #9 | 双数据对比 |\n| `eng.three_stat(title, stats, detail_items=None, source='')` | #10 | 三指标仪表盘 |\n| `eng.data_table(title, headers, rows, col_widths=None, source='', bottom_bar=None)` | #11 | 数据表格 |\n| `eng.metric_cards(title, cards, source='')` | #12 | 指标卡片行 |\n| `eng.metric_comparison(title, metrics, source='')` | #62 | 指标前后对比 |\n\n### C. Frameworks & Matrices(框架矩阵)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.matrix_2x2(title, quadrants, axis_labels=None, source='', bottom_bar=None)` | #13 | 四象限矩阵 |\n| `eng.table_insight(title, headers, rows, insights, col_widths=None, insight_title='启示:', source='', bottom_bar=None)` | #71 | 表格+洞见(推荐开篇用) |\n| `eng.pyramid(title, levels, source='', bottom_bar=None)` | #15 | 金字塔 |\n| `eng.process_chevron(title, steps, source='', bottom_bar=None)` | #16 | 流程箭头 |\n| `eng.venn(title, circles, overlap_label='', right_text=None, source='')` | #17 | 维恩图 |\n| `eng.temple(title, roof_text, pillar_names, foundation_text, pillar_colors=None, source='')` | #18 | 殿堂框架 |\n| `eng.staircase(title, steps, source='', bottom_bar=None)` | #15b | 阶梯进化图 |\n\n### D. Comparison & Evaluation(对比评估)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.side_by_side(title, options, source='')` | #19 | 左右对比 |\n| `eng.before_after(title, before_title, before_points, after_title, after_points, source='')` | #20 | 前后对比 |\n| `eng.pros_cons(title, pros_title, pros, cons_title, cons, conclusion=None, source='')` | #21 | 优劣分析 |\n| `eng.rag_status(title, headers, rows, source='')` | #22 | 红绿灯状态 |\n| `eng.scorecard(title, items, source='')` | #23 | 计分卡 |\n| `eng.checklist(title, columns, col_widths, rows, status_map=None, source='', bottom_bar=None)` | #61 | 检查清单 |\n| `eng.swot(title, quadrants, source='')` | #65 | SWOT 分析 |\n\n### E. Narrative(内容叙事)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.executive_summary(title, headline, items, source='')` | #24 | 执行摘要 |\n| `eng.key_takeaway(title, left_text, takeaways, source='')` | #25 | 核心洞见 |\n| `eng.quote(quote_text, attribution='')` | #26 | 引言页 |\n| `eng.two_column_text(title, columns, source='')` | #27 | 双栏文本(⚠️全局≤1张) |\n| `eng.four_column(title, items, source='')` | #28 | 四栏概览 |\n| `eng.numbered_list_panel(title, items, panel=None, source='')` | #69 | 编号列表+侧边栏 |\n\n### F. Timeline & Process(时间流程)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.timeline(title, milestones, source='', bottom_bar=None)` | #29 | 时间轴/路线图 |\n| `eng.vertical_steps(title, steps, source='', bottom_bar=None)` | #30 | 垂直步骤 |\n| `eng.cycle(title, phases, right_panel=None, source='')` | #31 | 循环图 |\n| `eng.funnel(title, stages, source='')` | #32 | 漏斗图 |\n| `eng.value_chain(title, stages, source='', bottom_bar=None)` | #67 | 价值链/水平流程 |\n\n### G. Team & Cases(团队专题)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.meet_the_team(title, members, source='')` | #33 | 团队介绍 |\n| `eng.case_study(title, sections, result_box=None, source='')` | #34 | 案例研究 |\n| `eng.action_items(title, actions, source='')` | #35 | 行动计划 |\n| `eng.case_study_image(title, sections, image_label, kpis=None, source='')` | #45 | 带图案例 |\n\n### H. Charts — BLOCK_ARC(圆形图表)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.donut(title, segments, center_label='', center_sub='', source='')` | #48 | 环形图(最多6段)|\n| `eng.pie(title, segments, source='')` | #64 | 饼图(最多6段)|\n| `eng.gauge(title, pct, label='', source='')` | #55 | 仪表盘 |\n\n### I. Charts — Bar/Line(柱线图)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.grouped_bar(title, categories, series, data, max_val, source='', bottom_bar=None)` | #37 | 分组柱状图(最多6类×3系列)|\n| `eng.stacked_bar(title, categories, series, data, source='', bottom_bar=None)` | #38 | 堆叠柱状图 |\n| `eng.horizontal_bar(title, items, source='', bottom_bar=None)` | #39 | 水平柱状图/排名 |\n| `eng.line_chart(title, categories, series, data, source='', bottom_bar=None)` | #50 | 折线图/趋势 |\n| `eng.stacked_area(title, categories, series, data, source='')` | #70 | 堆叠面积图 |\n\n### J. Charts — Advanced(高级图表)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.waterfall(title, items, source='')` | #49 | 瀑布图 |\n| `eng.pareto(title, items, source='')` | #51 | 帕累托图 |\n| `eng.progress_bars(title, items, source='')` | #52 | KPI 进度条 |\n| `eng.bubble(title, points, x_label='', y_label='', source='')` | #53 | 气泡图 |\n| `eng.risk_matrix(title, items, source='')` | #54 | 风险矩阵 |\n| `eng.harvey_ball(title, headers, rows, source='')` | #56 | Harvey Ball 状态表 |\n\n### K. Dashboards(仪表盘)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.dashboard_kpi(title, kpis, chart_data, takeaways, source='')` | #57 | KPI+图表仪表盘 |\n| `eng.dashboard_table(title, table_data, chart_data, factoids, source='')` | #58 | 表格+图表仪表盘 |\n\n### L. Image Layouts(图文版式)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.content_right_image(title, content, image_path=None, source='')` | #40 | 内容+右侧图 |\n| `eng.left_image_content(title, content, image_path=None, source='')` | #41 | 左图+内容 |\n| `eng.three_images(title, images, source='')` | #42 | 三图 |\n| `eng.image_four_points(title, points, image_path=None, source='')` | #43 | 图+四要点 |\n| `eng.hero_image(title, text, image_path=None, source='')` | #44 | 全宽背景图 |\n| `eng.two_col_image_text(title, items, source='')` | #68 | 双栏图文 |\n\n### M. Special(特殊版式)\n\n| Method | Pattern# | Description |\n|--------|----------|-------------|\n| `eng.stakeholder_map(title, stakeholders, source='')` | #59 | 利益相关方地图 |\n| `eng.decision_tree(title, nodes, source='')` | #60 | 决策树 |\n| `eng.icon_grid(title, items, source='')` | #63 | 图标网格(4×2或3×3)|\n| `eng.agenda(title, items, source='')` | #66 | 议程/会议大纲 |\n\n---\n\n## Content-to-Layout Quick Match\n\n| 内容类型 | 推荐版式 |\n|---------|---------|\n| 单个关键数据 | `big_number` |\n| 2个选项对比 | `side_by_side`, `before_after` |\n| 3–4个并列概念 | `table_insight`(⭐推荐), `metric_cards`, `four_column` |\n| 流程/步骤 | `process_chevron`, `vertical_steps`, `value_chain` |\n| 时间线 | `timeline` |\n| 数据表格 | `data_table`, `scorecard` |\n| 案例研究 | `case_study` |\n| 摘要/结论 | `executive_summary`, `key_takeaway` |\n| 多 KPI | `three_stat`, `dashboard_kpi` |\n| 时间序列数据 | `grouped_bar`, `line_chart`, `stacked_bar` |\n| 占比/构成 | `donut`, `pie` |\n| 风险/评估矩阵 | `risk_matrix`, `swot`, `matrix_2x2` |\n| 开篇高影响力 | `table_insight`(#1), `big_number`(#2), `key_takeaway`(#3) |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":8901,"content_sha256":"49c73504e92918a88730499a763b13c92c8eb7479ea571bff47298396071337c"},{"filename":"references/framework/guard-rails.md","content":"# Guard Rails — 10 Mandatory Production Rules\n\n> 框架级规范。所有 PPT 生成必须遵守,违反任何一条会导致文件损坏或视觉缺陷。\n\n## Rule 1: Bottom Bar Spacing(内容与底栏间距)\n\n**问题**:内容紧贴底部摘要栏,视觉上融为一体。\n\n**必须**:内容底部与底栏之间至少 **0.15\"** 间距。\n\n```python\nBOTTOM_BAR_GAP = Inches(0.2)\nbar_y = last_content_bottom + BOTTOM_BAR_GAP # ✅\n```\n\n**验证**:`bottom_bar_y >= last_content_bottom + Inches(0.15)`\n\n## Rule 2: Content Overflow Protection(内容溢出保护)\n\n**必须**:\n1. 右边距:`element.left + element.width ≤ Inches(0.8) + Inches(11.733) = Inches(12.533)`\n2. 底边距:`element.top + element.height ≤ Inches(6.95)`\n3. 文字在色块内时,文字框必须从色块边缘内缩至少 0.15\"\n\n```python\n# ✅ 多栏宽度计算(避免负宽度)\ncol_w = (CW - gap * (num_cols - 1)) / num_cols # NOT CW / num_cols\n```\n\n## Rule 3: No Bottom Whitespace(消除底部空白)\n\n**必须**:底部摘要栏位置在 `Inches(6.1)` 到 `Inches(6.4)` 之间。\n\n```python\nbar_y = max(content_bottom + Inches(0.15), Inches(6.1))\nbar_y = min(bar_y, Inches(6.4)) # ✅\n```\n\n## Rule 4: Legend Color Consistency(图例颜色一致)\n\n**必须**:图例标识符必须使用与图表完全相同颜色的色块(`add_rect()`),不得用纯文字 \"■\"。\n\n```python\n# ✅ 颜色匹配的图例色块\nadd_rect(s, lgx, legend_y, Inches(0.15), Inches(0.15), NAVY)\nadd_text(s, lgx + Inches(0.2), ..., '基准值', ...)\n```\n\n## Rule 5: Title Style Uniformity(标题样式统一)\n\n**必须**:所有内容页使用 `add_action_title()` / `aat()`(白底+黑字+下划线)。\n\n**禁止**:`add_navy_title_bar()` 在内容页使用(已废弃)。\n\n内容起始 y 值:使用 `add_action_title()` 后,内容从 **Inches(1.25)** 开始(不是 Inches(1.0))。\n\n## Rule 6: Axis Label Centering(坐标轴标签居中)\n\n**必须**:矩阵/网格图的坐标轴标签必须在整个轴的跨度上居中,不得用固定偏移。\n\n```python\n# ✅ Y轴标签垂直居中于完整网格高度\nadd_text(s, LM, grid_top, Inches(1.8), grid_h,\n 'Y轴↑', alignment=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE)\n```\n\n## Rule 7: Image Placeholder Required(图片占位符)\n\n**必须**:8页以上的 PPT,至少1张幻灯片含图片占位符(`add_image_placeholder()` 或自定义灰色框)。\n\n推荐位置:前2–3张内容页之后(作为视觉缓冲)。\n\n## Rule 8: Dynamic Sizing(动态尺寸计算)\n\n**必须**:数量可变的版式(清单行、流程节点、封面多行标题)必须动态计算尺寸。\n\n```python\n# ✅ 水平排列:动态宽度\nitem_w = (CW - gap * (n - 1)) / n\n\n# ✅ 垂直排列:动态高度\nitem_h = min(MAX_ITEM_H, available_h / max(n, 1))\n```\n\n**禁止**:`stage_w = Inches(2.0)` 固定宽度用于 N 个节点。\n\n## Rule 9: BLOCK_ARC for Circular Charts(圆形图表用 BLOCK_ARC)\n\n**必须**:环形图、饼图、仪表盘必须使用 `BLOCK_ARC` 原生形状(3–5 个形状/图表)。\n\n**禁止**:用 `add_rect()` 循环堆砌模拟圆弧(会产生数百到数千个形状,文件巨大且渲染慢)。\n\n详见 `references/layouts/charts-circular.md`。\n\n## Rule 10: Horizontal Item Overflow Protection(水平项溢出保护)\n\n**必须**:水平排列 N 项时,用动态计算防止负宽度。\n\n```python\nMIN_GAP = Inches(0.35)\nmax_item_w = (CW - MIN_GAP * max(n - 1, 1)) / max(n, 1)\nitem_w = min(PREFERRED_W, max_item_w) # ✅ 确保非负\n```\n\n**影响方法**:`process_chevron()`, `metric_cards()`, `icon_grid()`, `four_column()`。\n\n---\n\n## Anti-Corruption Rules(防止文件损坏)\n\n1. **禁止使用 `add_connector()`** — 会产生 `\u003cp:style>` 导致文件损坏,用 `add_hline()` 代替\n2. **所有形状必须调用 `_clean_shape()`** — 删除 p:style 防止效果引用\n3. **`eng.save()` 包含 full_cleanup** — 自动清理,无需手动操作\n4. **中文文本必须调用 `set_ea_font()`** — 设置 KaiTi 东亚字体\n\n```python\n# ✅ 安全的形状创建\nshape.fill.solid()\nshape.fill.fore_color.rgb = BG_GRAY\nshape.line.fill.background() # 移除边框\n_clean_shape(shape) # 移除 p:style\n```\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":4283,"content_sha256":"f6cff4b6ee00d19fd45d65b03dff4ef9d4595c977d872d2557ba33898515c2b2"},{"filename":"references/framework/planning-guide.md","content":"# Presentation Planning Guide\n\n> 框架级规范。版式选择、内容密度和结构规划。\n\n## Standard Slide Structures\n\n### 标准汇报(10–12 页)\n\n```\nSlide 1: 封面 (cover)\nSlide 2: 目录 (toc)\nSlide 3: 执行摘要 / 核心论点 (executive_summary 或 table_insight)\nSlides 4-7: 支撑论证(每页一个论点,变换版式)\nSlides 8-10: 案例/证据 (case_study 或 side_by_side)\nSlide 11: 综合/路线图 (timeline 或 process_chevron)\nSlide 12: 关键结论 + 结束 (key_takeaway 或 closing)\n```\n\n### 简短汇报(6–8 页)\n\n```\nSlide 1: 封面\nSlide 2: 执行摘要 (executive_summary)\nSlides 3-5: 核心内容(交替用 big_number, table_insight, side_by_side)\nSlide 6: 综合/时间轴\nSlide 7: 关键结论\nSlide 8: 结束页\n```\n\n### 关键规则\n\n- **最少 8 页**(实质性主题)\n- **一次性生成全部**:不允许生成后截断\n- **目录必须列出所有章节**\n\n## Layout Selection Rules\n\n### 开篇优先级(Slides 2–5 用高影响力版式)\n\n1. **`table_insight`** — 结构化论点+右侧灰色面板洞见(首选)\n2. **`big_number`** — 单个有影响力的数字\n3. **`key_takeaway`** — 左侧详情+右侧摘要\n\n### 数据必须用图表\n\n当内容含有日期/时段 + 数值/百分比时,**必须**用图表版式(#37–#39, #48–#56, #64, #70),不得用文字表格代替。\n\n### 图文版式要求\n\n8页以上 PPT,至少1张使用图文版式(#40–#47, #68)。\n\n### 版式多样性要求\n\n**相邻幻灯片不得使用同一版式**。\n\n### `two_column_text` 限制\n\n全局不超过1张。是最低视觉吸引力的版式。\n\n## Content Density Requirements\n\n每张内容页(非封面/结束)必须:\n\n1. **至少3个视觉块**(标题栏 + 内容区 + 底部摘要)\n2. **内容区使用率 ≥ 50%**(1.4\"–7.05\" 之间的可用空间)\n3. **Action Title 必须是完整句子**(表达洞见,不是标签)\n - ✅ `\"连接组约束的AI模型将自由参数减少90%,实现单细胞精度预测\"`\n - ❌ `\"连接组约束的AI模型\"`\n4. **具体数字**:用户提供了数字时必须突出展示\n5. **每页有 source 出处**\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2174,"content_sha256":"e6e361433bd64b1bd90c1b68c44afcc41bb1a1e3bd981dc3a0d2e5acd44e75e2"},{"filename":"references/INDEX.md","content":"# Knowledge Index — PPT Skill Reference Router\n\n> AI 在每个阶段应该读哪些文件。按需加载,不要全量读取。\n\n## Stage → Load Map\n\n| 阶段 | 必须读 | 可选读(按需) |\n|------|--------|--------------|\n| S1 需求定义 | `team/brand-guide.md`(了解约束)| — |\n| S2 结构设计 | `framework/engine-api.md`, `references/layout-matrix.yaml` | 涉及具体版式时读对应 `layouts/*.md` |\n| S3 内容填充 | `framework/guard-rails.md`, `experiences/*.md`(若存在)| `framework/planning-guide.md`(复杂结构时)|\n| S4 渲染 | 对应版式的 `layouts/*.md`(只读用到的)| `team/presentation-convention.md`(确认边距)|\n| S5 交付 | — | — |\n\n## File Directory\n\n```\nreferences/\n├── INDEX.md # 本文件,知识路由表\n├── layout-matrix.yaml # 版式×能力边界×字符预算(真相源)\n│\n├── team/ # 团队级(最稳定,所有项目继承)\n│ ├── brand-guide.md # 配色、字体、设计原则\n│ └── presentation-convention.md # 边距、必须元素、文件交付\n│\n├── framework/ # 框架级(中频更新)\n│ ├── engine-api.md # 67方法 API 速查表\n│ ├── guard-rails.md # 10条生产护栏(防崩溃规则)\n│ └── planning-guide.md # 版式选择、内容密度、结构规划\n│\n├── layouts/ # 版式级(按需加载)\n│ ├── structure.md # cover, toc, section_divider, closing\n│ ├── data-stats.md # big_number, two_stat, three_stat, data_table, metric_cards\n│ ├── frameworks.md # matrix_2x2, table_insight, process_chevron, temple, venn\n│ ├── comparison.md # side_by_side, before_after, pros_cons, rag_status, scorecard\n│ ├── narrative.md # executive_summary, key_takeaway, quote, four_column\n│ ├── timeline.md # timeline, vertical_steps, cycle, funnel, value_chain\n│ ├── team-cases.md # meet_the_team, case_study, action_items\n│ ├── charts-circular.md # donut, pie, gauge (BLOCK_ARC 规范)\n│ ├── charts-bar-line.md # grouped_bar, stacked_bar, horizontal_bar, line_chart\n│ ├── charts-advanced.md # waterfall, pareto, progress_bars, bubble, risk_matrix\n│ ├── dashboards.md # dashboard_kpi, dashboard_table\n│ ├── images.md # content_right_image, left_image_content, three_images\n│ └── special.md # stakeholder_map, decision_tree, icon_grid, swot\n│\n├── color-palette.md # 已有,颜色色板参考\n├── layout-catalog.md # 已有,版式目录索引\n│\n└── scripts/ # 门禁脚本(机读化执行,不可用 AI 自评替代)\n ├── gate_check.py # S4 门禁:QA 分类,输出 gate_result.json\n └── gate_check_s3.py # S3 门禁:API格式+数量+source检查,输出 gate_s3.json\n```\n\n## experiences/ Directory\n\n```\nexperiences/ # Self-Refinement 经验沉淀(AI 自动维护)\n├── overflow.md # 文字溢出类经验\n├── chart-limits.md # 图表数据段数限制经验\n├── layout-pitfalls.md # 版式特定踩坑\n└── cjk-issues.md # 中文渲染问题\n```\n\n## Quick Load Cheatsheet\n\n**只需要做一个封面** → 读 `team/brand-guide.md` 即可\n\n**做完整 10+ 页汇报** → S2 读 `engine-api.md` + `layout-matrix.yaml`;S3 读 `guard-rails.md`;S4 只读用到版式的文件\n\n**有图表** → S4 还要读 `layouts/charts-*.md`\n\n**之前做过同类汇报有报错** → S3 先读 `experiences/*.md`\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":3941,"content_sha256":"73bf929d216630cecd685571d74aa987036d19607da793f1e61f45dc245a5216"},{"filename":"references/layout-catalog.md","content":"# Layout Catalog — 72 Slide Types\n\n## Category A: Structure & Navigation\n| # | Name | Use Case |\n|---|------|----------|\n| 1 | Cover Page | Title, subtitle, date |\n| 2 | Action Title Page | Standard content slide |\n| 3 | Section Divider | Chapter transitions |\n| 4 | Table of Contents | Chapter list with descriptions |\n| 5 | Appendix Title | Back-matter separator |\n\n## Category B: Data & Statistics\n| # | Name | Use Case |\n|---|------|----------|\n| 6 | Big Number / Factoid | Single impactful metric |\n| 7 | Two-Stat Comparison | Side-by-side KPIs |\n| 8 | Three-Stat Dashboard | Three key indicators |\n| 9 | Data Table | Structured data rows |\n| 10 | Metric Cards Row | 3-4 parallel metric cards |\n| 11 | Traffic Light / RAG | Multi-project status |\n| 12 | Scorecard | Multi-dimension ratings |\n\n## Category C: Frameworks & Matrices\n| # | Name | Use Case |\n|---|------|----------|\n| 13 | 2x2 Matrix | BCG matrix, priority grid |\n| ~~14~~ | ~~Three-Pillar Framework~~ | _Retired v2.0.4 → use #71 Table+Insight_ |\n| 15 | Staircase Evolution | Ascending phases with icons + detail table (v2.0.5) |\n| 16 | Process Chevron | 3-5 step linear flow |\n| 17 | Venn Diagram | Concept intersections |\n| 18 | Temple / House Framework | Roof-pillar-foundation |\n\n## Category D: Comparison & Evaluation\n| # | Name | Use Case |\n|---|------|----------|\n| 19 | Side-by-Side Comparison | Option A vs Option B |\n| 20 | Before / After | White-bg editorial layout with vertical divider + black circle arrow — structured data rows + formula cards |\n| 21 | Pros and Cons | Advantage-risk analysis |\n| 22 | Traffic Light Status | Multi-item RAG status |\n\n## Category E: Content & Narrative\n| # | Name | Use Case |\n|---|------|----------|\n| 23 | Executive Summary | Core conclusions |\n| 24 | Key Takeaway with Detail | Left detail + right summary |\n| 25 | Quote / Insight Page | Prominent quotation |\n| 26 | Two-Column Text | Balanced dual topics |\n| 27 | Four-Column Overview | Four parallel dimensions |\n\n## Category F: Timeline & Process\n| # | Name | Use Case |\n|---|------|----------|\n| 28 | Timeline / Roadmap | Milestone planning |\n| 29 | Vertical Steps | Top-down action steps |\n| 30 | Cycle / Loop | PDCA, iterative processes |\n| 31 | Funnel | Conversion funnels |\n\n## Category G: Team & Special\n| # | Name | Use Case |\n|---|------|----------|\n| 32 | Meet the Team | Member profiles |\n| 33 | Case Study | SAR structure |\n| 34 | Action Items | Next steps checklist |\n| 35 | Closing / Thank You | End slide |\n| 36 | (Reserved) | Future expansion |\n\n## Category H: Data Charts\n| # | Name | Use Case |\n|---|------|----------|\n| 37 | Grouped Bar Chart | Multi-category comparison across time points |\n| 38 | Stacked Bar Chart | Part-to-whole composition over time |\n| 39 | Horizontal Bar Chart | Category ranking with long labels |\n\n## Category I: Image + Content Layouts _(v1.8)_\n| # | Name | Use Case |\n|---|------|----------|\n| 40 | Content + Right Image | Text left, image placeholder right |\n| 41 | Left Image + Content | Image left, text right |\n| 42 | Three Images + Descriptions | Three-column image comparison |\n| 43 | Image + Four Key Points | Central image with surrounding callouts |\n| 44 | Full-Width Image with Overlay | Hero image with text overlay |\n| 45 | Case Study with Image | SAR + visual + KPI boxes |\n| 46 | Quote with Background Image | Keynote-style quote slide |\n| 47 | Goals with Illustration | OKR list with supporting visual |\n\n## Category J: Advanced Data Visualization _(v1.8)_\n| # | Name | Use Case |\n|---|------|----------|\n| 48 | Donut Chart | Part-of-whole with center label |\n| 49 | Waterfall Chart | Revenue/profit bridge analysis |\n| 50 | Line / Trend Chart | Multi-series time-series trends |\n| 51 | Pareto Chart | 80/20 analysis with cumulative line |\n| 52 | Progress Bars / KPI Tracker | OKR tracking with status indicators |\n| 53 | Bubble / Scatter Plot | Two-variable comparison with size encoding |\n| 54 | Risk / Heat Matrix | Impact vs likelihood grid |\n| 55 | Gauge / Dial Chart | Single KPI health indicator |\n| 56 | Harvey Ball Status Table | Multi-criteria evaluation matrix |\n\n## Category K: Dashboard Layouts _(v1.8)_\n| # | Name | Use Case |\n|---|------|----------|\n| 57 | Dashboard: KPIs + Chart + Takeaways | Top KPIs, middle chart, bottom insights |\n| 58 | Dashboard: Table + Chart + Factoids | Left table, right chart, bottom factoid cards |\n\n## Category L: Visual Storytelling & Special _(v1.8)_\n| # | Name | Use Case |\n|---|------|----------|\n| 59 | Stakeholder Map | Influence vs interest matrix |\n| 60 | Issue / Decision Tree | MECE logic tree, diagnostic framework |\n| 61 | Five-Row Checklist / Status | Task completion tracker with progress bar |\n| 62 | Metric Comparison Row | Before/after with delta badges |\n| 63 | Icon Grid (3×2) | Capability overview, feature grid |\n| 64 | Pie Chart | Simple part-of-whole composition |\n| 65 | SWOT Analysis | Strengths/Weaknesses/Opportunities/Threats |\n| 66 | Agenda / Meeting Outline | Timed agenda with speaker assignments |\n| 67 | Value Chain / Horizontal Flow | End-to-end pipeline with KPIs |\n| 68 | Two-Column Image + Text Grid | 2×2 visual catalog |\n| 69 | Numbered List with Side Panel | Recommendations + highlight panel |\n| 70 | Stacked Area Chart | Cumulative trends over time |\n\n## Category M: Editorial Narrative _(v2.0.3)_\n| # | Name | Use Case |\n|---|------|----------|\n| 71 | Table + Insight Panel | Left data table + right gray-bg insight panel with chevron — ideal for opening analysis slides |\n| 72 | Multi-Bar Panel Chart | 2-3 side-by-side bar chart panels with CAGR trend arrows — multi-dimension data comparison |\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":5630,"content_sha256":"17c01ff23e6e6889387129d926c60eeed597f541a95eca9a7485187ee783d4e5"},{"filename":"references/layout-matrix.yaml","content":"# Layout Matrix — Themes × Layouts × Char Budget\n\n> 框架级真相源。AI 在 S2(结构设计)和 S3(内容填充)必须查此矩阵。\n> 不得猜测版式能力边界。\n\n## Default Theme\n\n```\ndefault_theme: mckinsey\n```\n\n## Themes\n\n| Theme | Style | Engine | Color Profile |\n|-------|-------|--------|---------------|\n| `mckinsey` | 麦肯锡咨询风 | MckEngine | navy-blue |\n| `tech_minimal` | 科技极简风(待扩展)| MckEngine | dark-mode |\n\n## Layout Capacity Matrix\n\n每种版式的 **char_budget**(字符数上限)是 S3 门禁的核心依据。\n\n| Layout Method | Pattern# | Max Items | Title Chars | Body/Cell Chars | Special Limits |\n|---------------|----------|-----------|-------------|-----------------|----------------|\n| `cover` | #1 | — | 40 | subtitle: 60, author: 30 | — |\n| `toc` | #6 | 6 items | 15 | item title: 20, desc: 40 | — |\n| `section_divider` | #5 | — | 40 | subtitle: 80 | — |\n| `closing` | #36 | — | 30 | message: 60 | — |\n| `big_number` | #8 | 4 bullets | 40 | number: 10, desc: 60, bullet: 40 | — |\n| `two_stat` | #9 | 2 stats | 40 | each number: 10, label: 20 | — |\n| `three_stat` | #10 | 3 stats | 40 | each number: 10, label: 20 | — |\n| `data_table` | #11 | 8 rows | 40 | header: 15, cell: 40 | — |\n| `metric_cards` | #12 | 4 cards | 40 | card title: 20, desc: 80 | — |\n| `matrix_2x2` | #13 | 4 quads | 40 | quadrant label: 15, desc: 80 | — |\n| `table_insight` | #71 | 6 rows | 40 | header: 15, cell: 40, insight: 60 | — |\n| `process_chevron` | #16 | 5 steps max | 40 | step label: 10, title: 20, desc: 50 | ⚠️ 超5步宽度自动收窄 |\n| `side_by_side` | #19 | 2×6 bullets | 40 | option title: 20, bullet: 50 | — |\n| `before_after` | #20 | 2×5 bullets | 40 | before/after title: 20, bullet: 50 | — |\n| `pros_cons` | #21 | 2×5 bullets | 40 | bullet: 50 | — |\n| `executive_summary` | #24 | 4 items | 40 | headline: 60, item title: 25, desc: 80 | — |\n| `key_takeaway` | #25 | 3 takeaways | 40 | left text: 3×80, takeaway: 60 | — |\n| `timeline` | #29 | 6 milestones | 40 | milestone label: 8, desc: 40 | — |\n| `vertical_steps` | #30 | 5 steps | 40 | step title: 25, desc: 80 | — |\n| `value_chain` | #67 | 5 stages | 40 | stage title: 15, desc: 60 | — |\n| `meet_the_team` | #33 | 4 members | 40 | name: 20, role: 25, bio: 100 | — |\n| `case_study` | #34 | 3 sections | 40 | section title: 15, desc: 120 | — |\n| `action_items` | #35 | 4 actions | 40 | action title: 25, timeline: 15, owner: 15, desc: 80 | — |\n| `donut` | #48 | **6 segments max** | 30 | segment label: 15 | ⚠️ 超6段必然溢出 |\n| `pie` | #64 | **6 segments max** | 30 | segment label: 15 | ⚠️ 超6段必然溢出 |\n| `grouped_bar` | #37 | 6 categories × 3 series | 30 | category: 8, series label: 15 | — |\n| `stacked_bar` | #38 | 6 categories | 30 | category: 8 | — |\n| `horizontal_bar` | #39 | 8 items | 30 | item label: 20 | — |\n| `four_column` | #28 | **4 columns max** | 40 | col title: 20, desc: 120 | ⚠️ 超4列会溢出 |\n| `icon_grid` | #63 | 8 items (4×2) 或 9 (3×3) | 40 | icon label: 15, desc: 50 | — |\n| `swot` | #65 | 4×4 points | 40 | each point: 50 | — |\n| `checklist` | #61 | 7 rows | 40 | cell: 30 | — |\n| `two_column_text` | #27 | 2×5 bullets | 40 | bullet: 60 | ⚠️ 全局≤1张 |\n\n## Usage in Gates\n\n**S2 结构门禁**:检查版式分配是否在 Max Items 范围内。\n\n**S3 内容门禁**:检查每个文本块字符数是否在 char_budget 内。\n\n```python\n# S3 门禁示例伪代码\nfor slide in content['slides']:\n layout = slide['layout']\n for field, text in slide['text_fields'].items():\n limit = LAYOUT_MATRIX[layout]['char_budget'].get(field, 80)\n if len(text) > limit:\n return GateFail(f\"Slide {slide['idx']} {field}: {len(text)} > {limit} chars\")\n```\n","content_type":"application/yaml; charset=utf-8","language":"yaml","size":3843,"content_sha256":"e154d13f93a91291e069836983eccacf360308893a14e862b52ce17bfad01b37"},{"filename":"references/scripts/gate_check_s3.py","content":"#!/usr/bin/env python3\n\"\"\"\ngate_check_s3.py — S3 内容门禁脚本(机读化)\n\n用法:\n python gate_check_s3.py \u003ccontent_json_path> \u003cproject_dir>\n\n输出:\n \u003cproject_dir>/gate_s3.json\n\ngate_s3.json 结构:\n{\n \"passed\": true/false,\n \"fail_items\": [ # 需要修复的问题列表\n {\n \"slide_idx\": 6,\n \"layout\": \"four_column\",\n \"check\": \"api_format\",\n \"message\": \"four_column items[0] 应为三元组 (num, title, desc),实际得到 2 个元素\"\n }\n ],\n \"pass_items\": [...] # 通过的检查项(供参考)\n}\n\n检查内容:\n 1. API 格式:确保各版式的 items/steps/segments 参数符合三元组要求\n 2. 数量约束:donut ≤6段、process_chevron ≤5步、grouped_bar ≤6类×3系列\n 3. 版式标签约束:process_chevron 标签不能含 \\\\n、timeline 最后标签 ≤6字符\n 4. 出处完整性:每张内容页有 source 非空\n\"\"\"\n\nimport sys\nimport os\nimport json\nfrom pathlib import Path\nfrom typing import List, Dict, Any\n\n\n# ─── 各版式 API 格式检查规则 ────────────────────────────────────────────────\n\ndef check_four_column(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"four_column items 必须是三元组 (num, col_title, desc)\"\"\"\n issues = []\n items = slide.get(\"items\", [])\n for i, item in enumerate(items):\n if not isinstance(item, (list, tuple)) or len(item) != 3:\n got = len(item) if isinstance(item, (list, tuple)) else \"非列表\"\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"four_column\",\n \"check\": \"api_format\",\n \"message\": (f\"four_column items[{i}] 应为三元组 (num, col_title, desc),\"\n f\"实际得到 {got} 个元素。\"\n f\"修复:加上编号,如 ('1', '标题', '描述内容')\"),\n })\n return issues\n\n\ndef check_executive_summary(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"executive_summary items 必须是三元组 (num, item_title, desc)\"\"\"\n issues = []\n items = slide.get(\"items\", [])\n for i, item in enumerate(items):\n if not isinstance(item, (list, tuple)) or len(item) != 3:\n got = len(item) if isinstance(item, (list, tuple)) else \"非列表\"\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"executive_summary\",\n \"check\": \"api_format\",\n \"message\": (f\"executive_summary items[{i}] 应为三元组 (num, item_title, desc),\"\n f\"实际得到 {got} 个元素。\"\n f\"修复:加上序号,如 ('1', '立即启动', '具体行动描述')\"),\n })\n return issues\n\n\ndef check_matrix_2x2(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"matrix_2x2 quadrants 必须是三元组 (label, bg_color, desc),不能是四元组\"\"\"\n issues = []\n quadrants = slide.get(\"quadrants\", [])\n for i, q in enumerate(quadrants):\n if not isinstance(q, (list, tuple)):\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"matrix_2x2\",\n \"check\": \"api_format\",\n \"message\": f\"matrix_2x2 quadrants[{i}] 应为列表/元组,实际类型 {type(q).__name__}\",\n })\n elif len(q) != 3:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"matrix_2x2\",\n \"check\": \"api_format\",\n \"message\": (f\"matrix_2x2 quadrants[{i}] 应为三元组 (label, bg_color, desc),\"\n f\"实际得到 {len(q)} 个元素。\"\n f\"修复:(label, LIGHT_BLUE, '描述文字'),只用3个元素\"),\n })\n if len(quadrants) != 4:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"matrix_2x2\",\n \"check\": \"count\",\n \"message\": f\"matrix_2x2 需要恰好 4 个象限,实际得到 {len(quadrants)} 个\",\n })\n return issues\n\n\ndef check_process_chevron(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"process_chevron: ≤5步,标签不含\\\\n,desc ≤50字符\"\"\"\n issues = []\n steps = slide.get(\"steps\", [])\n if len(steps) > 5:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"process_chevron\",\n \"check\": \"count\",\n \"message\": f\"process_chevron 最多 5 步,实际 {len(steps)} 步。修复:合并步骤或拆分成多页\",\n })\n for i, step in enumerate(steps):\n if not isinstance(step, (list, tuple)) or len(step) \u003c 3:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"process_chevron\",\n \"check\": \"api_format\",\n \"message\": f\"process_chevron steps[{i}] 应为三元组 (label, step_title, desc)\",\n })\n continue\n label, title, desc = step[0], step[1], step[2]\n if \"\\n\" in str(label):\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"process_chevron\",\n \"check\": \"label_newline\",\n \"message\": (f\"process_chevron steps[{i}] 标签不能含 \\\\n。\"\n f\"实际: {repr(label)}。修复:如 '1990-2010' 而非 '阶段一\\\\n1990-2010'\"),\n })\n if len(str(desc)) > 50:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"process_chevron\",\n \"check\": \"desc_length\",\n \"message\": (f\"process_chevron steps[{i}] desc 超 50 字符 ({len(str(desc))} 字)。\"\n f\"预览: '{str(desc)[:30]}...'\"),\n })\n return issues\n\n\ndef check_donut_pie(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"donut/pie segments ≤6\"\"\"\n issues = []\n layout = slide.get(\"layout\", \"\")\n segments = slide.get(\"segments\", [])\n if len(segments) > 6:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": layout,\n \"check\": \"count\",\n \"message\": (f\"{layout} 最多 6 段,实际 {len(segments)} 段。\"\n f\"修复:保留 top 5 + '其他' 合并\"),\n })\n return issues\n\n\ndef check_grouped_bar(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"grouped_bar categories ≤6,series ≤3\"\"\"\n issues = []\n categories = slide.get(\"categories\", [])\n series = slide.get(\"series\", [])\n if len(categories) > 6:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"grouped_bar\",\n \"check\": \"count\",\n \"message\": f\"grouped_bar 最多 6 个类别,实际 {len(categories)} 个\",\n })\n if len(series) > 3:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"grouped_bar\",\n \"check\": \"count\",\n \"message\": f\"grouped_bar 最多 3 个 series,实际 {len(series)} 个\",\n })\n return issues\n\n\ndef check_timeline_last_label(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"timeline 最后一个里程碑标签 ≤6 字符\"\"\"\n issues = []\n milestones = slide.get(\"milestones\", [])\n if milestones:\n last = milestones[-1]\n if isinstance(last, (list, tuple)) and len(last) >= 1:\n label = str(last[0])\n if len(label) > 6:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": \"timeline\",\n \"check\": \"last_label_length\",\n \"message\": (f\"timeline 最后里程碑标签 '{label}' 超 6 字符 ({len(label)} 字),\"\n f\"容易溢出右边界。修复:缩短为如 '36月'\"),\n })\n return issues\n\n\ndef check_source(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"每张内容页(非 cover/toc/section_divider/closing)有 source 非空\"\"\"\n issues = []\n layout = slide.get(\"layout\", \"\")\n skip_layouts = {\"cover\", \"toc\", \"section_divider\", \"closing\", \"appendix_title\"}\n if layout in skip_layouts:\n return issues\n source = slide.get(\"source\", \"\")\n if not source or source.strip() == \"\":\n issues.append({\n \"slide_idx\": idx,\n \"layout\": layout,\n \"check\": \"source_missing\",\n \"message\": f\"Slide {idx} ({layout}) 缺少 source 出处,每张内容页必须有 source\",\n })\n return issues\n\n\ndef check_action_title(slide: Dict, idx: int) -> List[Dict]:\n \"\"\"Action Title 应为完整句子(非 cover/section_divider/closing)\"\"\"\n issues = []\n layout = slide.get(\"layout\", \"\")\n skip_layouts = {\"cover\", \"toc\", \"section_divider\", \"closing\", \"appendix_title\"}\n if layout in skip_layouts:\n return issues\n title = slide.get(\"title\", \"\")\n if len(title) \u003c= 10:\n issues.append({\n \"slide_idx\": idx,\n \"layout\": layout,\n \"check\": \"title_too_short\",\n \"message\": (f\"Slide {idx} title '{title}' 太短(≤10字),应为完整洞见句。\"\n f\"如:'竞争格局分散,技术壁垒是核心差异化维度' 而非 '竞争格局'\"),\n })\n return issues\n\n\n# ─── 版式路由 ────────────────────────────────────────────────────────────────\n\nLAYOUT_CHECKERS = {\n \"four_column\": [check_four_column, check_source, check_action_title],\n \"executive_summary\": [check_executive_summary, check_source, check_action_title],\n \"matrix_2x2\": [check_matrix_2x2, check_source, check_action_title],\n \"process_chevron\": [check_process_chevron, check_source, check_action_title],\n \"donut\": [check_donut_pie, check_source, check_action_title],\n \"pie\": [check_donut_pie, check_source, check_action_title],\n \"grouped_bar\": [check_grouped_bar, check_source, check_action_title],\n \"stacked_bar\": [check_source, check_action_title],\n \"timeline\": [check_timeline_last_label, check_source, check_action_title],\n # 通用检查:有 source + title 长度\n \"big_number\": [check_source, check_action_title],\n \"table_insight\": [check_source, check_action_title],\n \"value_chain\": [check_source, check_action_title],\n \"key_takeaway\": [check_source, check_action_title],\n \"side_by_side\": [check_source, check_action_title],\n \"before_after\": [check_source, check_action_title],\n \"meet_the_team\": [check_source],\n \"case_study\": [check_source, check_action_title],\n \"data_table\": [check_source, check_action_title],\n \"scorecard\": [check_source, check_action_title],\n \"line_chart\": [check_source, check_action_title],\n \"horizontal_bar\": [check_source, check_action_title],\n \"waterfall\": [check_source, check_action_title],\n}\n\n\ndef run_gate_check_s3(content_json_path: str, project_dir: str) -> dict:\n \"\"\"执行 S3 内容门禁,返回 gate_s3 dict。\"\"\"\n if not os.path.exists(content_json_path):\n return {\n \"passed\": False,\n \"error\": f\"content.json 不存在: {content_json_path}\",\n \"fail_items\": [{\"check\": \"file_missing\", \"message\": f\"找不到 {content_json_path}\"}],\n \"pass_items\": [],\n }\n\n with open(content_json_path, \"r\", encoding=\"utf-8\") as f:\n try:\n content = json.load(f)\n except json.JSONDecodeError as e:\n return {\n \"passed\": False,\n \"error\": f\"content.json JSON 解析失败: {e}\",\n \"fail_items\": [{\"check\": \"json_parse\", \"message\": str(e)}],\n \"pass_items\": [],\n }\n\n slides = content.get(\"slides\", [])\n all_issues = []\n checked = []\n\n for slide in slides:\n idx = slide.get(\"idx\", \"?\")\n layout = slide.get(\"layout\", \"unknown\")\n checkers = LAYOUT_CHECKERS.get(layout, [check_source])\n\n slide_issues = []\n for checker in checkers:\n slide_issues.extend(checker(slide, idx))\n\n if not slide_issues:\n checked.append({\n \"slide_idx\": idx,\n \"layout\": layout,\n \"status\": \"ok\",\n })\n else:\n all_issues.extend(slide_issues)\n\n passed = len(all_issues) == 0\n result = {\n \"passed\": passed,\n \"total_slides\": len(slides),\n \"verdict\": \"PASS — 可进入 S4 渲染\" if passed\n else f\"FAIL — 必须修复 {len(all_issues)} 个问题后重新检查\",\n \"fail_items\": all_issues,\n \"pass_items\": checked,\n }\n return result\n\n\ndef main():\n if len(sys.argv) \u003c 3:\n print(\"用法: python gate_check_s3.py \u003ccontent_json_path> \u003cproject_dir>\")\n print(\"示例: python gate_check_s3.py ./ppt-project-foo/content.json ./ppt-project-foo/\")\n sys.exit(1)\n\n content_json_path = sys.argv[1]\n project_dir = sys.argv[2]\n\n Path(project_dir).mkdir(parents=True, exist_ok=True)\n output_path = os.path.join(project_dir, \"gate_s3.json\")\n\n print(f\"[gate_check_s3] 检查: {content_json_path}\")\n result = run_gate_check_s3(content_json_path, project_dir)\n\n with open(output_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(result, f, ensure_ascii=False, indent=2)\n\n print(f\"[gate_check_s3] Slides: {result.get('total_slides', '?')}\")\n print(f\"[gate_check_s3] Fail items: {len(result.get('fail_items', []))}\")\n print(f\"[gate_check_s3] Verdict: {result.get('verdict', '')}\")\n print(f\"[gate_check_s3] 结果已写入: {output_path}\")\n\n if result.get(\"fail_items\"):\n print(\"\\n[gate_check_s3] 需修复的问题:\")\n for item in result[\"fail_items\"]:\n print(f\" Slide {item.get('slide_idx')} [{item.get('layout')}]\"\n f\" [{item.get('check')}]: {item.get('message', '')[:100]}\")\n\n sys.exit(0 if result.get(\"passed\") else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":14015,"content_sha256":"5da0e015d5365f1ac2c1cd461eaf465ea909bad2ac20dc97cb77f1329d1e5fe9"},{"filename":"references/scripts/gate_check.py","content":"#!/usr/bin/env python3\n\"\"\"\ngate_check.py — S4 QA 门禁脚本(机读化)\n\n用法:\n python gate_check.py \u003cpptx_path> \u003cproject_dir>\n\n输出:\n \u003cproject_dir>/gate_result.json\n\ngate_result.json 结构:\n{\n \"passed\": true/false, # 唯一真相源,由程序派生,不由 AI 口头决定\n \"overall_score\": 92,\n \"checklist\": {\n \"user_code_errors\": 0, # 必须为 0 才能 passed\n \"engine_bug_errors\": 7, # 白名单豁免项,不阻塞通过\n \"warnings\": 1\n },\n \"user_code_error_detail\": [], # 需要 AI 修复的错误列表\n \"engine_bug_detail\": [...] # 白名单豁免的 engine 行为,仅供参考\n}\n\n门禁逻辑:\n passed = (user_code_errors == 0)\n engine_bug 类 errors 由 ENGINE_BUG_WHITELIST 枚举决定,不由 AI 口头判断。\n 如需添加新的豁免类别,修改此文件的 ENGINE_BUG_WHITELIST,不要口头声明豁免。\n\"\"\"\n\nimport sys\nimport os\nimport json\nfrom pathlib import Path\n\n# ─── 白名单:这些 error category 由 engine 内部设计行为产生,豁免 ───────────────\n# 每一条必须有文字证据,不允许口头声称豁免:\n# peer_font_inconsistency: engine 在 table_insight/process_chevron 等版式中\n# 有意使用不同字号(行标题 18pt vs 内容 14pt),是设计意图非代码错误。\n# 证据:mck_ppt/engine.py line ~529 table_insight 显式用 18pt header + 14pt body\n#\n# chart_legend_overflow (timeline only): timeline engine 最后一个里程碑标签\n# 使用固定右对齐定位(engine.py timeline 方法),QA 误报为溢出。\n# 证据:即使改为最短标签(如 '36月' 2字),仍然报 overflow 0.47\",\n# 与文字长度无关,是 engine 内置定位 bug,非用户代码问题。\n# 限制:仅豁免 timeline 版式的 chart_legend_overflow,其他版式不豁免。\nENGINE_BUG_WHITELIST = {\n \"peer_font_inconsistency\", # engine 设计:标题行 vs 内容行字号差异\n \"chart_legend_overflow\", # timeline engine bug:最后标签定位固定右对齐\n}\n\n# ─── 可配置的 WARNING 阈值 ──────────────────────────────────────────────────\nMAX_WARNINGS_ALLOWED = 3 # warnings 超过此数量时在报告中标注,但不阻塞通过\n\n\ndef run_gate_check(pptx_path: str, project_dir: str) -> dict:\n \"\"\"执行 S4 QA 门禁,返回 gate_result dict。\"\"\"\n # 添加引擎路径\n skill_dir = os.path.expanduser(\"~/.workbuddy/skills/mck-ppt-design\")\n if skill_dir not in sys.path:\n sys.path.insert(0, skill_dir)\n\n try:\n from mck_ppt.qa import PptQA\n except ImportError as e:\n return {\n \"passed\": False,\n \"error\": f\"无法导入 mck_ppt.qa: {e}. 请确认 mck-ppt-design skill 已安装\",\n \"user_code_errors\": 999,\n }\n\n if not os.path.exists(pptx_path):\n return {\n \"passed\": False,\n \"error\": f\"文件不存在: {pptx_path}\",\n \"user_code_errors\": 999,\n }\n\n # 运行 QA\n report = PptQA(pptx_path).run()\n\n # 分类 errors\n user_code_errors = []\n engine_bug_errors = []\n\n for issue in report.errors:\n if issue.category in ENGINE_BUG_WHITELIST:\n engine_bug_errors.append({\n \"slide\": issue.slide_num,\n \"category\": issue.category,\n \"message\": issue.message[:120],\n \"whitelist_reason\": f\"在 ENGINE_BUG_WHITELIST 中:{issue.category}\",\n })\n else:\n user_code_errors.append({\n \"slide\": issue.slide_num,\n \"category\": issue.category,\n \"message\": issue.message[:120],\n \"shape\": getattr(issue, \"shape_name\", \"\"),\n })\n\n warnings_detail = [\n {\n \"slide\": w.slide_num,\n \"category\": w.category,\n \"message\": w.message[:100],\n }\n for w in report.warnings\n ]\n\n passed = len(user_code_errors) == 0\n\n result = {\n \"passed\": passed,\n \"overall_score\": report.overall_score,\n \"pptx_path\": str(pptx_path),\n \"checklist\": {\n \"user_code_errors\": len(user_code_errors),\n \"engine_bug_errors\": len(engine_bug_errors),\n \"warnings\": len(report.warnings),\n },\n \"verdict\": \"PASS — 可进入 S5 交付\" if passed\n else f\"FAIL — 必须修复 {len(user_code_errors)} 个 user_code_errors 后重新渲染\",\n \"user_code_error_detail\": user_code_errors,\n \"engine_bug_detail\": engine_bug_errors,\n \"warnings_detail\": warnings_detail,\n }\n\n return result\n\n\ndef main():\n if len(sys.argv) \u003c 3:\n print(\"用法: python gate_check.py \u003cpptx_path> \u003cproject_dir>\")\n print(\"示例: python gate_check.py ./output.pptx ./ppt-project-foo/\")\n sys.exit(1)\n\n pptx_path = sys.argv[1]\n project_dir = sys.argv[2]\n\n # 确保项目目录存在\n Path(project_dir).mkdir(parents=True, exist_ok=True)\n output_path = os.path.join(project_dir, \"gate_result.json\")\n\n print(f\"[gate_check] 检查: {pptx_path}\")\n result = run_gate_check(pptx_path, project_dir)\n\n # 写出结果\n with open(output_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(result, f, ensure_ascii=False, indent=2)\n\n # 打印摘要\n print(f\"[gate_check] Score: {result.get('overall_score', 'N/A')}\")\n print(f\"[gate_check] User code errors: {result['checklist'].get('user_code_errors', '?')}\")\n print(f\"[gate_check] Engine bug errors (豁免): {result['checklist'].get('engine_bug_errors', '?')}\")\n print(f\"[gate_check] Warnings: {result['checklist'].get('warnings', '?')}\")\n print(f\"[gate_check] Verdict: {result.get('verdict', '')}\")\n print(f\"[gate_check] 结果已写入: {output_path}\")\n\n if result.get(\"user_code_error_detail\"):\n print(\"\\n[gate_check] 需修复的 user_code_errors:\")\n for e in result[\"user_code_error_detail\"]:\n print(f\" Slide {e['slide']} [{e['category']}]: {e['message'][:80]}\")\n\n # 退出码:0=通过,1=失败\n sys.exit(0 if result.get(\"passed\") else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":6313,"content_sha256":"2896daeaea753ff4ed1aed450856fc0dad58f7e865b866bf1d7aa7d6f94f2174"},{"filename":"references/team/brand-guide.md","content":"# Brand Guide — McKinsey PPT Design\n\n> 团队级规范。适用于所有 PPT 项目,每次生成必须遵守。\n\n## Color Palette\n\n| Color Name | Hex | RGB | Usage |\n|-----------|-----|-----|-------|\n| **NAVY** | #051C2C | (5, 28, 44) | Primary: titles, circles, primary elements |\n| **WHITE** | #FFFFFF | (255, 255, 255) | Backgrounds, text on navy |\n| **BLACK** | #000000 | (0, 0, 0) | Lines, text separators |\n| **DARK_GRAY** | #333333 | (51, 51, 51) | Body text, descriptions |\n| **MED_GRAY** | #666666 | (102, 102, 102) | Secondary text, labels |\n| **LINE_GRAY** | #CCCCCC | (204, 204, 204) | Table row separators |\n| **BG_GRAY** | #F2F2F2 | (242, 242, 242) | Background panels, takeaway areas |\n| ~~CYAN~~ | #00A9F4 | — | **DEPRECATED** — 不要使用 |\n\n**Key Rule**: NAVY (#051C2C) 用于所有圆形标记、action titles、section headers、TOC 高亮。\n\n### Accent Colors(3+ 并列项时使用)\n\n| Accent | Hex | 配对浅色背景 | 用途 |\n|--------|-----|------------|------|\n| ACCENT_BLUE | #006BA6 | #E3F2FD | 第1项 |\n| ACCENT_GREEN | #007A53 | #E8F5E9 | 第2项 |\n| ACCENT_ORANGE | #D46A00 | #FFF3E0 | 第3项 |\n| ACCENT_RED | #C62828 | #FFEBEE | 第4项/警告 |\n\n**规则**:只有幻灯片有3+并列项时才用 accent colors。卡片内 body text 始终用 DARK_GRAY。\n\n## Typography\n\n```\n英文标题:Georgia (serif)\n英文正文:Arial (sans-serif)\n中文全部:KaiTi 楷体(备选:SimSun 宋体)\n```\n\n### 字号层级(严格遵守,不得使用其他字号)\n\n| 字号 | 类型 | 使用场景 |\n|------|------|---------|\n| 44pt | 封面大标题 | Cover slide only,Georgia |\n| 28pt | 章节标题 | TOC title,Georgia |\n| 24pt | 副标题 | Cover subtitle |\n| 22pt | Action Title | 所有内容页标题,bold,Georgia |\n| 18pt | Sub-Header | 栏头、章节名 |\n| 16pt | 强调文字 | 底部 takeaway,bold |\n| 14pt | 正文 | **PRIMARY SIZE**,所有主体内容 |\n| 9pt | 脚注 | Source 出处,仅用灰色 |\n\n## Design Philosophy\n\n1. **极简** — 无阴影、无3D、无渐变、无装饰性色块\n2. **一致** — 同类内容同字号同颜色,全局统一\n3. **层级** — 标题22pt → 栏头18pt → 正文14pt → 脚注9pt\n4. **平面** — 纯实色填充,无任何效果\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2263,"content_sha256":"08e2bab53ddaaae4246a5e7b3bd0c29b507477652abada8657e04ed1eab8ee35"},{"filename":"references/team/presentation-convention.md","content":"# Presentation Convention — McKinsey PPT Design\n\n> 团队级汇报体例规范。适用于所有 PPT 项目。\n\n## Slide Dimensions\n\n```python\nprs.slide_width = Inches(13.333) # 宽屏 16:9\nprs.slide_height = Inches(7.5)\n```\n\n## Standard Margins\n\n| Position | Size | Usage |\n|----------|------|-------|\n| Left margin (LM) | 0.8\" | 默认左边距 |\n| Right margin | 0.8\" | 默认右边距 |\n| Content width (CW) | 11.733\" | LM 到右边距 |\n| Content start (below title) | 1.3–1.4\" | 内容区上边界 |\n| Source line | 7.05\" | 出处文字基线 |\n| Action title top | 0.15\" | 标题顶部 |\n| Action title height | 0.9\" | 标题栏高度 |\n\n## Mandatory Slide Elements\n\n每个内容页(除 Cover 和 Closing 外)**必须**包含:\n\n| 元素 | 方法 | 位置 |\n|------|------|------|\n| Action Title | `add_action_title()` / `aat()` | 顶部 0.15\" |\n| 标题分隔线 | Action title 自动包含 | 1.05\" |\n| 内容区 | 版式相关 | 1.3\"–6.5\" |\n| Source 出处 | `add_source()` | 7.05\" |\n| 页码 | `add_page_number(n, total)` | 右下角 |\n\n## Title Style Rule\n\n**唯一标准**:所有内容页使用 `add_action_title()` (白底+黑字+下划线)。\n\n**已废弃**:`add_navy_title_bar()` (深蓝底+白字) — 禁止在内容页使用。\n\n## Source Attribution\n\n每个内容页必须有 source,格式:`Source: [来源机构/报告 年份]`\n\n## Page Numbering\n\n```python\ndef add_page_number(slide, num, total):\n add_text(slide, Inches(12.2), Inches(7.1), Inches(1), Inches(0.3),\n f\"{num}/{total}\", font_size=Pt(9), font_color=MED_GRAY,\n alignment=PP_ALIGN.RIGHT)\n```\n\n## Slide Count Guidelines\n\n- **标准汇报**:10–12页\n- **简短汇报**:6–8页\n- **最少**:8页(实质性主题)\n- **时长约束**:约 1 分钟/页\n\n## File Delivery\n\nPython 引擎路径:`~/.workbuddy/skills/mck-ppt-design`\n\n```python\nimport sys, os\nsys.path.insert(0, os.path.expanduser('~/.workbuddy/skills/mck-ppt-design'))\nfrom mck_ppt import MckEngine\n```\n\n`eng.save()` 自动执行三层 XML 清理,无需手动调用 `full_cleanup()`。\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":2091,"content_sha256":"75abb9a580581abae4f6efbb83bad3bf3a49bfbfec7aa63e3a3c3fa26fa98135"},{"filename":"run_qa_tests.py","content":"#!/usr/bin/env python3\n# Copyright 2024-2026 Kaku Li (https://github.com/likaku)\n# Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.\n# Part of \"Mck-ppt-design-skill\" (McKinsey PPT Design Framework).\n# NOTICE: This file must be retained in all copies or substantial portions.\n#\n\"\"\"\nPPT QA Test Runner — generates all 55 layout methods and runs QA analysis.\n\nUsage:\n python run_qa_tests.py # Run all, print summary\n python run_qa_tests.py --json report.json # Also save JSON report\n python run_qa_tests.py --only cover,toc # Run specific methods only\n python run_qa_tests.py --stress # Stress test with long content\n\"\"\"\n\nimport os\nimport sys\nimport json\nimport time\nimport argparse\nfrom datetime import datetime\n\n# Add parent dir to path so we can import mck_ppt\nsys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))\n\nfrom pptx.util import Inches\nfrom mck_ppt import MckEngine\nfrom mck_ppt.constants import *\nfrom mck_ppt.qa import PptQA, QAReport, Severity\n\n# ── Test Data Fixtures ─────────────────────────────────────────────────\n\n# Short / normal content\nNORMAL = {\n \"title\": \"Analysis of Market Dynamics\",\n \"subtitle\": \"Q1 2026 Strategic Review\",\n \"author\": \"Strategy Team\",\n \"long_title\": \"Comprehensive Analysis of Global Market Dynamics and Competitive Landscape Assessment for Strategic Decision Making\",\n \"bullet_3\": [\"Revenue grew 23% YoY\", \"Market share expanded to 34%\", \"Customer satisfaction at 92%\"],\n \"bullet_5\": [\"Revenue grew 23% YoY\", \"Market share expanded to 34%\", \"Customer satisfaction at 92%\", \"Employee engagement improved 15%\", \"Operating margin expanded 200bps\"],\n \"bullet_7\": [\"Revenue grew 23% YoY driven by new product launches\", \"Market share expanded to 34% across all segments\", \"Customer satisfaction at 92% reflecting service improvements\", \"Employee engagement improved 15% with new culture initiatives\", \"Operating margin expanded 200bps through cost optimization\", \"Digital transformation achieved 85% adoption rate\", \"Sustainability targets met across all business units\"],\n \"stats_2\": [(\"$4.2B\", \"Revenue\", \"+23% YoY\"), (\"34%\", \"Market Share\", \"+5pp vs prior year\")],\n \"stats_3\": [(\"$4.2B\", \"Revenue\", \"+23% YoY\"), (\"34%\", \"Market Share\", \"+5pp\"), (\"92%\", \"CSAT Score\", \"Industry benchmark: 78%\")],\n \"headers\": [\"Metric\", \"Q1\", \"Q2\", \"Q3\", \"Q4\"],\n \"rows_4\": [\n [\"Revenue ($M)\", \"245\", \"312\", \"298\", \"356\"],\n [\"Gross Margin\", \"62%\", \"64%\", \"63%\", \"65%\"],\n [\"Net Income ($M)\", \"42\", \"55\", \"48\", \"67\"],\n [\"Headcount\", \"1,234\", \"1,312\", \"1,298\", \"1,356\"],\n ],\n \"rows_8\": [\n [\"Revenue ($M)\", \"245\", \"312\", \"298\", \"356\"],\n [\"Cost of Goods\", \"93\", \"112\", \"110\", \"125\"],\n [\"Gross Margin\", \"62%\", \"64%\", \"63%\", \"65%\"],\n [\"OpEx ($M)\", \"89\", \"95\", \"92\", \"98\"],\n [\"EBITDA ($M)\", \"63\", \"105\", \"96\", \"133\"],\n [\"Net Income ($M)\", \"42\", \"55\", \"48\", \"67\"],\n [\"Headcount\", \"1,234\", \"1,312\", \"1,298\", \"1,356\"],\n [\"NPS Score\", \"72\", \"75\", \"74\", \"78\"],\n ],\n \"cards_3\": [\n (\"Revenue\", \"$4.2B\", \"+23%\"),\n (\"Market Share\", \"34%\", \"+5pp\"),\n (\"CSAT\", \"92%\", \"↑ 8pts\"),\n ],\n \"cards_5\": [\n (\"Revenue\", \"$4.2B\", \"+23%\"),\n (\"Market Share\", \"34%\", \"+5pp\"),\n (\"CSAT\", \"92%\", \"↑ 8pts\"),\n (\"Retention\", \"96%\", \"+2pp\"),\n (\"NPS\", \"78\", \"Industry avg: 62\"),\n ],\n}\n\n# Stress test content (long text, many items)\nSTRESS = {\n \"long_text\": \"This is a comprehensive analysis spanning multiple domains including market dynamics, competitive positioning, customer segmentation, product portfolio optimization, and operational efficiency improvements. \" * 3,\n \"bullet_12\": [f\"Critical initiative #{i+1}: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor\" for i in range(12)],\n \"rows_15\": [[f\"Row {i+1}\"] + [f\"{(i+1)*10+j}\" for j in range(4)] for i in range(15)],\n}\n\n\n# ── Layout Test Definitions ────────────────────────────────────────────\ndef build_all_tests(eng: MckEngine, stress: bool = False):\n \"\"\"\n Generate one slide per layout method. Returns list of (method_name, success, error).\n \"\"\"\n results = []\n\n def _run(name, fn, *args, **kwargs):\n try:\n fn(*args, **kwargs)\n results.append((name, True, None))\n except Exception as e:\n results.append((name, False, str(e)))\n\n # ── Structure ──\n _run(\"cover\", eng.cover, title=NORMAL[\"title\"], subtitle=NORMAL[\"subtitle\"], author=NORMAL[\"author\"])\n _run(\"cover_long\", eng.cover, title=NORMAL[\"long_title\"], subtitle=NORMAL[\"subtitle\"], author=NORMAL[\"author\"], date=\"March 2026\")\n _run(\"section_divider\", eng.section_divider, section_label=\"01\", title=\"Market Overview\", subtitle=\"Current landscape analysis\")\n _run(\"toc\", eng.toc, title=\"Agenda\", items=[(\"1\", \"Market Overview\", \"Current landscape\"), (\"2\", \"Competitive Analysis\", \"Key players\"), (\"3\", \"Strategy\", \"Recommended actions\"), (\"4\", \"Financials\", \"Projections and targets\")])\n _run(\"appendix_title\", eng.appendix_title, title=\"Appendix\", subtitle=\"Supporting Materials\")\n _run(\"closing\", eng.closing, title=\"Thank You\", message=\"Questions and discussion\", source_text=\"Contact: [email protected]\")\n\n # ── Data ──\n _run(\"big_number\", eng.big_number, title=\"Revenue Growth\", number=\"$4.2B\", unit=\"Revenue\", description=\"Year-over-year growth of 23%, exceeding target by 5 percentage points\", source=\"Q1 2026 Financial Report\")\n _run(\"two_stat\", eng.two_stat, title=\"Key Metrics\", stats=NORMAL[\"stats_2\"], source=\"Internal data\")\n _run(\"three_stat\", eng.three_stat, title=\"Performance Dashboard\", stats=NORMAL[\"stats_3\"], source=\"Analytics team\")\n _run(\"data_table_4\", eng.data_table, title=\"Quarterly Performance\", headers=NORMAL[\"headers\"], rows=NORMAL[\"rows_4\"], source=\"Finance\")\n _run(\"data_table_8\", eng.data_table, title=\"Detailed Financial Summary\", headers=NORMAL[\"headers\"], rows=NORMAL[\"rows_8\"], source=\"Finance\")\n _run(\"metric_cards_3\", eng.metric_cards, title=\"Key Performance Indicators\", cards=NORMAL[\"cards_3\"], source=\"Dashboard\")\n _run(\"metric_cards_5\", eng.metric_cards, title=\"Extended KPI Dashboard\", cards=NORMAL[\"cards_5\"], source=\"Dashboard\")\n # rag_status: rows = (name, status_color:RGBColor, *values, note)\n _run(\"rag_status\", eng.rag_status, title=\"Project Status\", headers=[\"Project\", \"Status\", \"Owner\", \"Due Date\"], rows=[(\"Alpha\", ACCENT_GREEN, \"J. Smith\", \"Mar 2026\"), (\"Beta\", ACCENT_ORANGE, \"K. Lee\", \"Apr 2026\"), (\"Gamma\", ACCENT_RED, \"M. Wang\", \"May 2026\"), (\"Delta\", ACCENT_GREEN, \"A. Chen\", \"Jun 2026\")], source=\"PMO\")\n # scorecard: items = (name, score_str, pct_float)\n _run(\"scorecard\", eng.scorecard, title=\"Team Scorecard\", items=[(\"Revenue Target\", \"92%\", 0.92), (\"Customer NPS\", \"78\", 0.78), (\"Employee Engagement\", \"85%\", 0.85), (\"Cost Efficiency\", \"65%\", 0.65), (\"Innovation Index\", \"55%\", 0.55)], source=\"HR\")\n\n # ── Framework ──\n # matrix_2x2: quadrants = (label, bg_color:RGBColor, description)\n _run(\"matrix_2x2\", eng.matrix_2x2, title=\"Priority Matrix\", quadrants=[(\"High Impact / Low Effort\", LIGHT_BLUE, \"Quick wins, process automation\"), (\"High Impact / High Effort\", LIGHT_GREEN, \"Market expansion, M&A targets\"), (\"Low Impact / Low Effort\", LIGHT_ORANGE, \"Minor fixes, low priority\"), (\"Low Impact / High Effort\", LIGHT_RED, \"Deprioritize, avoid\")], source=\"Strategy\")\n # #14 three_pillar retired (v2.0.4) → use #71 table_insight\n _run(\"table_insight_strategic\", eng.table_insight, title=\"Strategic Pillars\", headers=[\"Pillar\", \"Key Initiatives\", \"Expected Impact\"], rows=[[\"Growth\", \"Market expansion\\nNew products\\nPartnerships\", \"Revenue +20%\"], [\"Efficiency\", \"Cost optimization\\nProcess automation\\nShared services\", \"Margin +5pp\"], [\"Innovation\", \"R&D investment\\nDigital transformation\\nAI/ML adoption\", \"New revenue streams\"]], insights=[\"Growth is the top priority for FY2026\", \"Efficiency gains fund innovation investments\", \"AI/ML adoption accelerates all three pillars\"], source=\"CEO Office\")\n # pyramid: levels = (label, description, width_inches:float)\n _run(\"pyramid\", eng.pyramid, title=\"Capability Pyramid\", levels=[(\"Vision\", \"Long-term direction\", 4.0), (\"Strategy\", \"How we compete\", 6.0), (\"Execution\", \"Day-to-day delivery\", 8.0), (\"Foundation\", \"People and culture\", 10.0)], source=\"Org Design\")\n # process_chevron: steps = (label, step_title, description)\n _run(\"process_chevron\", eng.process_chevron, title=\"Delivery Process\", steps=[(\"01\", \"Discovery\", \"Research & insights\"), (\"02\", \"Design\", \"Solution architecture\"), (\"03\", \"Build\", \"Development sprint\"), (\"04\", \"Test\", \"QA & validation\"), (\"05\", \"Launch\", \"Go-to-market\")], source=\"PMO\")\n # venn: circles = (label, points:list[str], x, y, w, h)\n _run(\"venn\", eng.venn, title=\"Strategic Overlap\", circles=[(\"Customer\\nNeeds\", [\"Usability\", \"Reliability\", \"Value\"], Inches(1.0), Inches(1.5), Inches(4.0), Inches(3.5)), (\"Our\\nCapabilities\", [\"Technology\", \"Talent\", \"IP\"], Inches(4.0), Inches(1.5), Inches(4.0), Inches(3.5)), (\"Market\\nOpportunity\", [\"Growth areas\", \"Unmet needs\"], Inches(2.5), Inches(3.0), Inches(4.0), Inches(3.0))], overlap_label=\"Sweet Spot\", source=\"Strategy\")\n _run(\"temple\", eng.temple, title=\"Operating Model\", roof_text=\"Customer Excellence\", pillar_names=[\"People\", \"Process\", \"Technology\"], foundation_text=\"Culture & Values\", source=\"COO\")\n # cycle: phases = (label, x_inches, y_inches)\n _run(\"cycle\", eng.cycle, title=\"Innovation Cycle\", phases=[(\"Ideate\", 0.0, 1.5), (\"Validate\", 3.5, 1.5), (\"Build\", 3.5, 4.0), (\"Scale\", 0.0, 4.0)], source=\"Innovation Lab\")\n # funnel: stages = (name, count_label, pct_float)\n _run(\"funnel\", eng.funnel, title=\"Sales Funnel\", stages=[(\"Awareness\", \"10,000\", 1.0), (\"Interest\", \"4,200\", 0.42), (\"Consideration\", \"1,800\", 0.18), (\"Decision\", \"720\", 0.072), (\"Purchase\", \"312\", 0.031)], source=\"Sales Ops\")\n\n # ── Comparison ──\n _run(\"side_by_side\", eng.side_by_side, title=\"Option Comparison\", options=[(\"Option A: Build\", [\"Full control\", \"Higher initial cost\", \"6-month timeline\", \"Custom to needs\"]), (\"Option B: Buy\", [\"Faster deployment\", \"Lower initial cost\", \"Vendor dependency\", \"Standard features\"])], source=\"Tech team\")\n _run(\"before_after\", eng.before_after, title=\"Process Transformation\", before_title=\"Current State\", before_points=[\"Manual data entry\", \"5-day processing time\", \"12% error rate\"], after_title=\"Future State\", after_points=[\"Automated ingestion\", \"Same-day processing\", \"\u003c1% error rate\"], source=\"Ops\")\n # pros_cons: conclusion = (label, text) or None\n _run(\"pros_cons\", eng.pros_cons, title=\"Partnership Assessment\", pros_title=\"Advantages\", pros=[\"Market access\", \"Brand synergy\", \"Shared R&D costs\"], cons_title=\"Risks\", cons=[\"Cultural mismatch\", \"IP concerns\", \"Integration complexity\"], conclusion=(\"Recommendation\", \"Proceed with phased approach\"), source=\"BD Team\")\n # swot: quadrants = (label, accent_color, light_bg, points:list[str])\n _run(\"swot\", eng.swot, title=\"SWOT Analysis\", quadrants=[(\"Strengths\", ACCENT_BLUE, LIGHT_BLUE, [\"Market leader\", \"Strong brand\", \"Talent pool\"]), (\"Weaknesses\", ACCENT_ORANGE, LIGHT_ORANGE, [\"Legacy systems\", \"High costs\", \"Slow innovation\"]), (\"Opportunities\", ACCENT_GREEN, LIGHT_GREEN, [\"Emerging markets\", \"AI adoption\", \"Partnerships\"]), (\"Threats\", ACCENT_RED, LIGHT_RED, [\"New entrants\", \"Regulation\", \"Recession risk\"])], source=\"Strategy\")\n\n # ── Narrative ──\n # executive_summary: items = (num, item_title, description)\n _run(\"executive_summary\", eng.executive_summary, title=\"Executive Summary\", headline=\"Strong Q1 performance driven by product innovation and market expansion\", items=[(\"1\", \"Revenue\", \"Grew 23% YoY to $4.2B, exceeding guidance by 5pp\"), (\"2\", \"Market Share\", \"Expanded to 34%, up 5pp from prior year\"), (\"3\", \"Outlook\", \"Raising FY guidance by 8% based on strong pipeline\")], source=\"CFO Report\")\n _run(\"key_takeaway\", eng.key_takeaway, title=\"Key Insights\", left_text=\"Our analysis of 500+ enterprise customers reveals three critical success factors for digital transformation programs.\", takeaways=[\"Executive sponsorship is the #1 predictor of success\", \"Cross-functional teams outperform siloed approaches by 3x\", \"Iterative delivery reduces time-to-value by 60%\"], source=\"Research\")\n _run(\"quote\", eng.quote, quote_text=\"The best way to predict the future is to create it.\", attribution=\"Peter Drucker\")\n # two_column_text: columns = (letter, col_title, points:list[str])\n _run(\"two_column_text\", eng.two_column_text, title=\"Market Analysis\", columns=[(\"A\", \"Domestic Market\", [\"Revenue grew 23% YoY\", \"Market share expanded to 34%\", \"Customer satisfaction at 92%\"]), (\"B\", \"International Market\", [\"APAC grew 45% driven by China and India\", \"EMEA stable at 12% growth\", \"LATAM emerging as new growth driver\"])], source=\"Market Intelligence\")\n # four_column: items = (num, col_title, description)\n _run(\"four_column\", eng.four_column, title=\"Capability Assessment\", items=[(\"1\", \"People\", \"World-class talent with deep domain expertise across 30+ industries\"), (\"2\", \"Process\", \"Proven methodologies refined over thousands of engagements\"), (\"3\", \"Technology\", \"Cutting-edge tools and platforms enabling data-driven insights\"), (\"4\", \"Culture\", \"Collaborative environment fostering innovation and excellence\")], source=\"HR\")\n _run(\"meet_the_team\", eng.meet_the_team, title=\"Leadership Team\", members=[(\"Alice Chen\", \"CEO\", \"20 years in tech\"), (\"Bob Kim\", \"CFO\", \"Ex-Goldman Sachs\"), (\"Carol Liu\", \"CTO\", \"PhD CS, MIT\"), (\"David Park\", \"COO\", \"Supply chain expert\")], source=\"HR\")\n # case_study: sections = (letter, section_title, description)\n _run(\"case_study\", eng.case_study, title=\"Client Success Story\", sections=[(\"S\", \"Challenge\", \"Legacy systems causing 40% operational overhead\"), (\"A\", \"Approach\", \"Phased digital transformation over 18 months\"), (\"R\", \"Solution\", \"Cloud migration + process automation + AI analytics\")], result_box=(\"Result\", \"60% cost reduction, 3x faster time-to-market\"), source=\"Client reference (anonymized)\")\n # action_items: actions = (action_title, timeline, description, owner)\n _run(\"action_items\", eng.action_items, title=\"Next Steps\", actions=[(\"Complete market research\", \"Week 1-2\", \"Conduct competitive analysis and customer surveys\", \"Strategy Team\"), (\"Draft partnership proposal\", \"Week 2-3\", \"Prepare terms and business case\", \"BD Team\"), (\"Build financial model\", \"Week 3-4\", \"Revenue projections and ROI analysis\", \"Finance\"), (\"Present to board\", \"Week 5\", \"Final recommendation and approval\", \"CEO\")], source=\"PMO\")\n\n # ── Timeline ──\n _run(\"timeline\", eng.timeline, title=\"Project Roadmap\", milestones=[(\"Q1\", \"Discovery & Planning\"), (\"Q2\", \"Design & Build MVP\"), (\"Q3\", \"Testing & Iteration\"), (\"Q4\", \"Launch & Scale\")], source=\"PMO\")\n _run(\"vertical_steps\", eng.vertical_steps, title=\"Implementation Steps\", steps=[(\"Step 1\", \"Assess current state\", \"Conduct diagnostic\"), (\"Step 2\", \"Design future state\", \"Define target architecture\"), (\"Step 3\", \"Build roadmap\", \"Prioritize initiatives\"), (\"Step 4\", \"Execute pilot\", \"Validate approach\"), (\"Step 5\", \"Scale rollout\", \"Full deployment\")], source=\"Delivery\")\n\n # ── Charts ──\n # grouped_bar: data[cat_idx][series_idx] — each inner list has len(series) elements\n _run(\"grouped_bar\", eng.grouped_bar, title=\"Revenue by Region\", categories=[\"NA\", \"EMEA\", \"APAC\", \"LATAM\"], series=[(\"2024\", NAVY), (\"2025\", ACCENT_BLUE)], data=[[120, 145], [85, 92], [95, 130], [45, 62]], source=\"Finance\")\n # stacked_bar: data[period_idx][series_idx] — each inner list has len(series) elements\n _run(\"stacked_bar\", eng.stacked_bar, title=\"Revenue Composition\", periods=[\"Q1\", \"Q2\", \"Q3\", \"Q4\"], series=[(\"Product\", NAVY), (\"Services\", ACCENT_BLUE), (\"License\", ACCENT_GREEN)], data=[[50, 30, 20], [48, 30, 22], [45, 30, 25], [44, 28, 28]], source=\"Finance\")\n # horizontal_bar: items = (name, pct_int, bar_color)\n _run(\"horizontal_bar\", eng.horizontal_bar, title=\"Customer Satisfaction by Segment\", items=[(\"Enterprise\", 92, NAVY), (\"Mid-Market\", 85, ACCENT_BLUE), (\"SMB\", 78, ACCENT_GREEN), (\"Consumer\", 71, ACCENT_ORANGE)], source=\"CX Team\")\n\n # ── Image layouts ──\n _run(\"content_right_image\", eng.content_right_image, title=\"Product Overview\", subtitle=\"Next-Generation Platform\", bullets=[\"AI-powered analytics\", \"Real-time dashboards\", \"Cross-platform support\"], takeaway=\"Launch scheduled for Q3 2026\", source=\"Product\")\n # three_images: items = (caption_title, description, image_label)\n _run(\"three_images\", eng.three_images, title=\"Office Locations\", items=[(\"New York HQ\", \"500+ employees serving East Coast clients\", \"NYC Office\"), (\"London\", \"200+ employees covering EMEA region\", \"London Office\"), (\"Singapore\", \"150+ employees for APAC expansion\", \"SG Office\")], source=\"Facilities\")\n _run(\"image_four_points\", eng.image_four_points, title=\"Innovation Lab\", image_label=\"Lab Photo\", points=[(\"AI/ML\", \"Advanced machine learning capabilities\"), (\"Cloud\", \"Hybrid cloud infrastructure\"), (\"Data\", \"Petabyte-scale analytics\"), (\"Security\", \"Zero-trust architecture\")], source=\"CTO\")\n _run(\"full_width_image\", eng.full_width_image, title=\"Campus Overview\", image_label=\"Aerial View\", overlay_text=\"Our state-of-the-art facility\", source=\"Facilities\")\n # case_study_image: sections = (label, text, accent_color)\n _run(\"case_study_image\", eng.case_study_image, title=\"Digital Transformation\", sections=[(\"Challenge\", \"Outdated infrastructure with 99.5% uptime target missed\", ACCENT_RED), (\"Solution\", \"Cloud-native platform with microservices architecture\", ACCENT_GREEN)], image_label=\"Architecture Diagram\", kpis=[(\"Uptime\", \"99.99%\"), (\"Latency\", \"-80%\")], source=\"CTO\")\n _run(\"quote_bg_image\", eng.quote_bg_image, image_label=\"Background\", quote_text=\"Innovation distinguishes between a leader and a follower.\", attribution=\"Steve Jobs\")\n # goals_illustration: goals = (goal_title, description, accent_color)\n _run(\"goals_illustration\", eng.goals_illustration, title=\"2026 Strategic Goals\", goals=[(\"Revenue $5B+\", \"23% growth target through organic and inorganic channels\", ACCENT_BLUE), (\"Market Leader\", \"Top 3 in all segments globally\", ACCENT_GREEN), (\"Best Employer\", \"Top 10 workplace globally with 90%+ engagement\", ACCENT_ORANGE)], image_label=\"Vision Image\", source=\"CEO\")\n\n # ── Advanced Charts ──\n # donut: segments = (pct_float, color, label)\n _run(\"donut\", eng.donut, title=\"Revenue Mix\", segments=[(0.35, NAVY, \"Product A\"), (0.25, ACCENT_BLUE, \"Product B\"), (0.20, ACCENT_GREEN, \"Product C\"), (0.20, BG_GRAY, \"Other\")], center_label=\"$4.2B\", center_sub=\"Total Revenue\", source=\"Finance\")\n _run(\"waterfall\", eng.waterfall, title=\"Bridge Analysis\", items=[(\"Q1 Base\", 100, NAVY), (\"Price\", 15, ACCENT_GREEN), (\"Volume\", 25, ACCENT_GREEN), (\"FX\", -8, ACCENT_RED), (\"Costs\", -12, ACCENT_RED), (\"Q1 Total\", 120, NAVY)], source=\"FP&A\")\n _run(\"line_chart\", eng.line_chart, title=\"Monthly Trend\", x_labels=[\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"], y_labels=[\"0\", \"25\", \"50\", \"75\", \"100\"], values=[20, 35, 45, 60, 72, 88], legend_label=\"Revenue ($M)\", source=\"Finance\")\n _run(\"pareto\", eng.pareto, title=\"Issue Analysis\", items=[(\"System Errors\", 45), (\"User Errors\", 30), (\"Network Issues\", 15), (\"Hardware\", 7), (\"Other\", 3)], source=\"Ops\")\n # kpi_tracker: kpis = (name, pct_float, detail, status_key)\n _run(\"kpi_tracker\", eng.kpi_tracker, title=\"KPI Dashboard\", kpis=[(\"Revenue\", 0.92, \"$4.2B of $4.6B target\", \"on\"), (\"Margin\", 0.85, \"65% vs 70% target\", \"risk\"), (\"NPS\", 0.78, \"78 vs 85 target\", \"risk\"), (\"Retention\", 0.96, \"96% exceeding 95% target\", \"on\")], source=\"Dashboard\")\n # bubble: bubbles = (x_pct, y_pct, size_inches, label, color)\n _run(\"bubble\", eng.bubble, title=\"Portfolio Analysis\", bubbles=[(0.8, 0.9, 1.2, \"Product A\", NAVY), (0.4, 0.7, 0.8, \"Product B\", ACCENT_BLUE), (0.6, 0.4, 1.0, \"Product C\", ACCENT_GREEN), (0.2, 0.6, 0.5, \"Product D\", ACCENT_ORANGE)], x_label=\"Market Size\", y_label=\"Growth Rate\", source=\"Strategy\")\n # risk_matrix: grid_lights must be RGBColor (not None), risks = (row, col, name)\n _run(\"risk_matrix\", eng.risk_matrix, title=\"Risk Assessment\", grid_colors=[[ACCENT_GREEN, ACCENT_GREEN, ACCENT_ORANGE], [ACCENT_GREEN, ACCENT_ORANGE, ACCENT_RED], [ACCENT_ORANGE, ACCENT_RED, ACCENT_RED]], grid_lights=[[LIGHT_GREEN, LIGHT_GREEN, LIGHT_ORANGE], [LIGHT_GREEN, LIGHT_ORANGE, LIGHT_RED], [LIGHT_ORANGE, LIGHT_RED, LIGHT_RED]], risks=[(2, 1, \"Supply Chain\"), (1, 2, \"Cyber\"), (0, 0, \"Regulatory\")], source=\"Risk Team\")\n # gauge: benchmarks = (label, value_str, color)\n _run(\"gauge\", eng.gauge, title=\"Performance Score\", score=78, benchmarks=[(\"Industry Avg\", \"62\", ACCENT_ORANGE), (\"Target\", \"85\", ACCENT_GREEN)], source=\"Analytics\")\n _run(\"harvey_ball_table\", eng.harvey_ball_table, title=\"Vendor Assessment\", criteria=[\"Price\", \"Quality\", \"Support\", \"Innovation\"], options=[\"Vendor A\", \"Vendor B\", \"Vendor C\"], scores=[[4,3,2],[3,4,3],[2,2,4],[3,3,3]], source=\"Procurement\")\n # pie: segments = (pct_float, color, label, sub_label)\n _run(\"pie\", eng.pie, title=\"Market Share\", segments=[(0.34, NAVY, \"Us\", \"$1.4B\"), (0.28, ACCENT_BLUE, \"Competitor A\", \"$1.2B\"), (0.22, ACCENT_GREEN, \"Competitor B\", \"$0.9B\"), (0.16, BG_GRAY, \"Others\", \"$0.7B\")], source=\"Market Research\")\n _run(\"stacked_area\", eng.stacked_area, title=\"Revenue Trend\", years=[\"2022\", \"2023\", \"2024\", \"2025\"], series_data=[(\"Product\", [60, 75, 90, 110], NAVY), (\"Services\", [30, 38, 45, 55], ACCENT_BLUE), (\"License\", [20, 25, 30, 38], ACCENT_GREEN)], source=\"Finance\")\n\n # ── Dashboard & Special ──\n _run(\"dashboard_kpi_chart\", eng.dashboard_kpi_chart, title=\"Executive Dashboard\", kpi_cards=[(\"Revenue\", \"$4.2B\", \"+23%\", NAVY), (\"Margin\", \"65%\", \"+2pp\", ACCENT_BLUE), (\"NPS\", \"78\", \"+8\", ACCENT_GREEN)], source=\"Analytics\")\n # stakeholder_map: quadrants = (label_cn, label_en, bg_color, members:list[str])\n _run(\"stakeholder_map\", eng.stakeholder_map, title=\"Stakeholder Map\", quadrants=[(\"高权力/高兴趣\", \"High Power / High Interest\", LIGHT_BLUE, [\"Board of Directors\", \"CEO\", \"Key Investors\"]), (\"高权力/低兴趣\", \"High Power / Low Interest\", LIGHT_GREEN, [\"Regulators\", \"Government Agencies\"]), (\"低权力/高兴趣\", \"Low Power / High Interest\", LIGHT_ORANGE, [\"Customers\", \"Media\", \"Employees\"]), (\"低权力/低兴趣\", \"Low Power / Low Interest\", BG_GRAY, [\"General Public\", \"Industry Associations\"])], source=\"Comms\")\n # decision_tree: root = (label,); branches = (L1_title, L1_metric, L1_color, children:list[(name, metric)])\n _run(\"decision_tree\", eng.decision_tree, title=\"Decision Framework\", root=(\"Strategic Investment?\",), branches=[(\"Build In-House\", \"$2M / 6mo\", ACCENT_BLUE, [(\"Team Available\", \"Start Q2\"), (\"Need Hiring\", \"Start Q3\")]), (\"Partner / Buy\", \"$1M / 3mo\", ACCENT_GREEN, [(\"Existing Partner\", \"Negotiate terms\"), (\"New Vendor\", \"RFP process\")])], source=\"Strategy\")\n _run(\"checklist\", eng.checklist, title=\"Project Readiness\", columns=[\"Workstream\", \"Status\", \"Owner\", \"Due\"], col_widths=[0.35, 0.2, 0.25, 0.2], rows=[(\"Data Migration\", \"✅ Complete\", \"J. Smith\", \"Done\"), (\"API Integration\", \"🔄 In Progress\", \"K. Lee\", \"Mar 30\"), (\"UAT Testing\", \"⏳ Pending\", \"M. Wang\", \"Apr 15\"), (\"Go-Live Prep\", \"⏳ Pending\", \"A. Chen\", \"Apr 30\")], source=\"PMO\")\n _run(\"metric_comparison\", eng.metric_comparison, title=\"Year-over-Year\", metrics=[(\"Revenue\", \"$3.4B\", \"$4.2B\", \"+23%\"), (\"Margin\", \"63%\", \"65%\", \"+2pp\"), (\"Headcount\", \"1,100\", \"1,356\", \"+23%\")], source=\"Finance\")\n # icon_grid: items = (item_title, description, accent_color)\n _run(\"icon_grid\", eng.icon_grid, title=\"Capability Map\", items=[(\"Analytics\", \"Data-driven insights platform\", ACCENT_BLUE), (\"AI/ML\", \"Predictive models and automation\", ACCENT_GREEN), (\"Cloud\", \"Scalable hybrid infrastructure\", ACCENT_ORANGE), (\"Security\", \"Zero-trust architecture\", ACCENT_RED), (\"Mobile\", \"Cross-platform native apps\", NAVY), (\"Global\", \"Multi-region deployment\", ACCENT_BLUE)], source=\"CTO\")\n # agenda: headers = (label, width), items = (*values, item_type)\n _run(\"agenda\", eng.agenda, title=\"Workshop Agenda\", headers=[(\"Time\", 0.15), (\"Topic\", 0.55), (\"Presenter\", 0.30)], items=[(\"9:00\", \"Welcome & Objectives\", \"CEO\", \"key\"), (\"9:30\", \"Market Overview\", \"Strategy Lead\", \"normal\"), (\"10:30\", \"Break\", \"—\", \"break\"), (\"10:45\", \"Workshop Session\", \"All Participants\", \"key\"), (\"12:00\", \"Wrap-up & Next Steps\", \"COO\", \"normal\")], source=\"EA\")\n # value_chain: stages = (stage_title, description, accent_color)\n _run(\"value_chain\", eng.value_chain, title=\"Value Chain\", stages=[(\"Inbound\\nLogistics\", \"Sourcing & receiving\", ACCENT_BLUE), (\"Operations\", \"Manufacturing & assembly\", ACCENT_GREEN), (\"Outbound\\nLogistics\", \"Distribution & delivery\", ACCENT_ORANGE), (\"Marketing\\n& Sales\", \"Go-to-market execution\", ACCENT_RED), (\"Service\", \"Post-sale support\", NAVY)], source=\"COO\")\n _run(\"numbered_list_panel\", eng.numbered_list_panel, title=\"Key Recommendations\", items=[(\"Accelerate digital transformation program by 6 months\", \"Leverage existing vendor partnerships to reduce timeline\"), (\"Invest $50M in AI capabilities\", \"Focus on customer-facing applications first\"), (\"Restructure operations for efficiency\", \"Target 15% cost reduction over 18 months\")])\n # two_col_image_grid: items = (card_title, description, accent_color, image_label)\n _run(\"two_col_image_grid\", eng.two_col_image_grid, title=\"Project Showcase\", items=[(\"Phase 1: Discovery\", \"Research and stakeholder interviews\", ACCENT_BLUE, \"Research Photo\"), (\"Phase 2: Design\", \"Solution architecture and prototyping\", ACCENT_GREEN, \"Design Photo\"), (\"Phase 3: Build\", \"Agile development sprints\", ACCENT_ORANGE, \"Build Photo\"), (\"Phase 4: Launch\", \"Deployment and training\", NAVY, \"Launch Photo\")], source=\"Delivery\")\n\n # ── Stress tests (if enabled) ──\n if stress:\n _run(\"stress_data_table_15\", eng.data_table, title=\"Extended Data (15 rows)\", headers=NORMAL[\"headers\"], rows=STRESS[\"rows_15\"], source=\"Stress test\")\n _run(\"stress_long_title\", eng.executive_summary, title=STRESS[\"long_text\"][:200], headline=STRESS[\"long_text\"][:300], items=[(\"Key Finding\", STRESS[\"long_text\"])], source=\"Stress test\")\n\n return results\n\n\n# ── Main ───────────────────────────────────────────────────────────────\ndef main():\n parser = argparse.ArgumentParser(description=\"PPT QA Test Runner\")\n parser.add_argument(\"--json\", help=\"Output JSON report path\")\n parser.add_argument(\"--only\", help=\"Comma-separated method names to test\")\n parser.add_argument(\"--stress\", action=\"store_true\", help=\"Include stress tests\")\n parser.add_argument(\"--outdir\", default=\"qa_output\", help=\"Output directory\")\n args = parser.parse_args()\n\n outdir = args.outdir\n os.makedirs(outdir, exist_ok=True)\n timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n pptx_path = os.path.join(outdir, f\"qa_test_{timestamp}.pptx\")\n\n print(f\"\\n{'='*70}\")\n print(f\" McKinsey PPT Design — QA Test Runner\")\n print(f\" {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\")\n print(f\"{'='*70}\\n\")\n\n # Step 1: Generate test deck\n print(\"Step 1: Generating test deck with all layout methods...\")\n t0 = time.time()\n eng = MckEngine(total_slides=80)\n results = build_all_tests(eng, stress=args.stress)\n\n # Filter if --only specified\n if args.only:\n allowed = set(args.only.split(\",\"))\n results = [(n, s, e) for n, s, e in results if n in allowed]\n\n # Save\n eng.save(pptx_path)\n gen_time = time.time() - t0\n print(f\" → Generated {len(results)} layouts in {gen_time:.1f}s → {pptx_path}\")\n\n # Report generation failures\n gen_failures = [(n, e) for n, s, e in results if not s]\n gen_success = [(n, e) for n, s, e in results if s]\n if gen_failures:\n print(f\"\\n ⚠️ {len(gen_failures)} generation failure(s):\")\n for name, err in gen_failures:\n print(f\" ❌ {name}: {err}\")\n print(f\" ✅ {len(gen_success)} layouts generated successfully\\n\")\n\n # Step 2: Run QA analysis\n print(\"Step 2: Running QA analysis...\")\n t1 = time.time()\n qa = PptQA(pptx_path)\n report = qa.run()\n qa_time = time.time() - t1\n print(f\" → Analyzed {report.total_slides} slides in {qa_time:.1f}s\\n\")\n\n # Step 3: Print results\n print(\"Step 3: Results\\n\")\n report.print_summary()\n\n # Annotated summary: map slide numbers to layout method names\n print(f\"\\n{'─'*70}\")\n print(f\" Layout Method → Slide Score Mapping\")\n print(f\"{'─'*70}\")\n slide_num = 0\n for name, success, _ in results:\n if success:\n slide_num += 1\n score = report.slide_scores.get(slide_num, 100)\n status = \"✅\" if score >= 90 else \"⚠️ \" if score >= 70 else \"❌\"\n issues_for_slide = [i for i in report.issues if i.slide_num == slide_num]\n issue_summary = \"\"\n if issues_for_slide:\n cats = set(i.category for i in issues_for_slide)\n issue_summary = f\" [{', '.join(cats)}]\"\n print(f\" {status} Slide {slide_num:2d}: {name:30s} → {score:3d}/100{issue_summary}\")\n\n # Step 4: Save JSON\n json_path = args.json or os.path.join(outdir, f\"qa_report_{timestamp}.json\")\n full_report = {\n \"meta\": {\n \"timestamp\": timestamp,\n \"pptx_path\": pptx_path,\n \"generation_time_sec\": round(gen_time, 1),\n \"qa_analysis_time_sec\": round(qa_time, 1),\n \"total_layouts_tested\": len(results),\n \"generation_failures\": len(gen_failures),\n },\n \"layout_results\": [\n {\"method\": n, \"generated\": s, \"error\": e, \"slide_num\": i+1 if s else None}\n for i, (n, s, e) in enumerate(results)\n ],\n \"qa_report\": json.loads(report.to_json()),\n }\n with open(json_path, \"w\", encoding=\"utf-8\") as f:\n json.dump(full_report, f, indent=2, ensure_ascii=False)\n print(f\"\\n 📄 JSON report: {json_path}\")\n\n # Final verdict\n print(f\"\\n{'='*70}\")\n if report.passed and not gen_failures:\n print(f\" ✅ ALL CHECKS PASSED — Score: {report.overall_score}/100\")\n elif report.passed:\n print(f\" ⚠️ QA passed (Score: {report.overall_score}/100) but {len(gen_failures)} generation failure(s)\")\n else:\n print(f\" ❌ {len(report.errors)} ERROR(s) found — Score: {report.overall_score}/100\")\n print(f\"{'='*70}\\n\")\n\n return 0 if report.passed and not gen_failures else 1\n\n\nif __name__ == \"__main__\":\n sys.exit(main())","content_type":"text/x-python; charset=utf-8","language":"python","size":31207,"content_sha256":"0a08ced7fe3aaedffad067c25277b148c896781d17010d50ef89681883a03c2c"},{"filename":"scripts/generate_icons.py","content":"#!/usr/bin/env python3\n# Copyright 2024-2026 Kaku Li (https://github.com/likaku)\n# Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.\n# Part of \"Mck-ppt-design-skill\" (McKinsey PPT Design Framework).\n# NOTICE: This file must be retained in all copies or substantial portions.\n#\n\"\"\"Generate white-on-transparent PNG icons for Staircase Evolution slides.\n\nAll icons: 200×200 px, transparent background, white strokes (~6px).\nStore under assets/icons/ for use with eng.pyramid() icon parameter.\n\nIcon library:\n Business icons:\n - icon_person_bust.png — Single person bust (B2B decision maker)\n - icon_shield_check.png — Shield with checkmark (quality certification)\n - icon_people_group.png — Group of people (all consumers)\n Civilization icons:\n - icon_factory_gear.png — Gear/cog wheel (Industrial Age)\n - icon_circuit_chip.png — Microchip with pins (Information Age)\n - icon_ai_brain.png — Brain with neural nodes (Intelligence Age)\n\"\"\"\nimport math\nimport os\n\nfrom PIL import Image, ImageDraw\n\nOUT_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),\n 'assets', 'icons')\nos.makedirs(OUT_DIR, exist_ok=True)\n\nSIZE = 200\nWHITE = (255, 255, 255, 255)\nTRANSPARENT = (0, 0, 0, 0)\nSTROKE = 6 # line thickness\n\n\n# ═══════════════════════════════════════════\n# Business Icons\n# ═══════════════════════════════════════════\n\ndef icon_person_bust():\n \"\"\"Single person bust — represents B2B procurement decision maker.\"\"\"\n img = Image.new('RGBA', (SIZE, SIZE), TRANSPARENT)\n d = ImageDraw.Draw(img)\n cx = SIZE // 2\n\n head_r = 28\n head_cy = 58\n d.ellipse(\n [cx - head_r, head_cy - head_r, cx + head_r, head_cy + head_r],\n outline=WHITE, width=STROKE\n )\n\n body_top = head_cy + head_r + 12\n body_w = 72\n d.arc(\n [cx - body_w, body_top - 10, cx + body_w, body_top + body_w + 40],\n start=180, end=360, fill=WHITE, width=STROKE\n )\n\n cut_y = body_top + 42\n d.line(\n [(cx - body_w + 4, cut_y), (cx + body_w - 4, cut_y)],\n fill=WHITE, width=STROKE\n )\n return img\n\n\ndef icon_shield_check():\n \"\"\"Shield with checkmark — represents quality certification.\"\"\"\n img = Image.new('RGBA', (SIZE, SIZE), TRANSPARENT)\n d = ImageDraw.Draw(img)\n cx = SIZE // 2\n\n shield_pts = [\n (cx - 52, 35), (cx + 52, 35), (cx + 52, 100),\n (cx, 165), (cx - 52, 100),\n ]\n d.line(shield_pts + [shield_pts[0]], fill=WHITE, width=STROKE, joint='curve')\n\n check_pts = [(cx - 24, 98), (cx - 6, 120), (cx + 30, 72)]\n d.line(check_pts, fill=WHITE, width=STROKE + 1, joint='curve')\n return img\n\n\ndef icon_people_group():\n \"\"\"Three people — represents all consumers / broad audience.\"\"\"\n img = Image.new('RGBA', (SIZE, SIZE), TRANSPARENT)\n d = ImageDraw.Draw(img)\n cx = SIZE // 2\n\n # Center person\n d.ellipse([cx - 18, 38, cx + 18, 74], outline=WHITE, width=STROKE)\n d.arc([cx - 38, 78, cx + 38, 140], start=180, end=360, fill=WHITE, width=STROKE)\n\n # Left person\n lx = cx - 50\n d.ellipse([lx - 15, 52, lx + 15, 82], outline=WHITE, width=STROKE - 1)\n d.arc([lx - 32, 86, lx + 32, 140], start=180, end=360, fill=WHITE, width=STROKE - 1)\n\n # Right person\n rx = cx + 50\n d.ellipse([rx - 15, 52, rx + 15, 82], outline=WHITE, width=STROKE - 1)\n d.arc([rx - 32, 86, rx + 32, 140], start=180, end=360, fill=WHITE, width=STROKE - 1)\n\n # Smile arc\n d.arc([cx - 30, 142, cx + 30, 168], start=0, end=180, fill=WHITE, width=STROKE - 2)\n return img\n\n\n# ═══════════════════════════════════════════\n# Civilization Icons\n# ═══════════════════════════════════════════\n\ndef icon_factory_gear():\n \"\"\"Gear/cog wheel — represents Industrial Age.\"\"\"\n img = Image.new('RGBA', (SIZE, SIZE), TRANSPARENT)\n d = ImageDraw.Draw(img)\n cx, cy = SIZE // 2, SIZE // 2\n\n outer_r, inner_r, teeth = 72, 52, 8\n tooth_half_angle = math.pi / (teeth * 2.5)\n\n pts = []\n for i in range(teeth):\n angle = 2 * math.pi * i / teeth\n for offset in [-tooth_half_angle, tooth_half_angle]:\n a = angle + offset\n pts.append((cx + outer_r * math.cos(a), cy + outer_r * math.sin(a)))\n valley_angle = angle + math.pi / teeth\n for offset in [-tooth_half_angle * 0.6, tooth_half_angle * 0.6]:\n a = valley_angle + offset\n pts.append((cx + inner_r * math.cos(a), cy + inner_r * math.sin(a)))\n\n d.line(pts + [pts[0]], fill=WHITE, width=STROKE, joint='curve')\n\n hole_r = 20\n d.ellipse(\n [cx - hole_r, cy - hole_r, cx + hole_r, cy + hole_r],\n outline=WHITE, width=STROKE\n )\n return img\n\n\ndef icon_circuit_chip():\n \"\"\"Microchip with pins — represents Information Age.\"\"\"\n img = Image.new('RGBA', (SIZE, SIZE), TRANSPARENT)\n d = ImageDraw.Draw(img)\n cx, cy = SIZE // 2, SIZE // 2\n\n chip_half = 42\n d.rounded_rectangle(\n [cx - chip_half, cy - chip_half, cx + chip_half, cy + chip_half],\n radius=6, outline=WHITE, width=STROKE\n )\n\n die_half = 22\n d.rectangle(\n [cx - die_half, cy - die_half, cx + die_half, cy + die_half],\n outline=WHITE, width=STROKE - 1\n )\n\n pin_count, pin_len = 4, 20\n pin_gap = (chip_half * 2) / (pin_count + 1)\n for i in range(1, pin_count + 1):\n offset = -chip_half + pin_gap * i\n d.line([(cx + offset, cy - chip_half), (cx + offset, cy - chip_half - pin_len)],\n fill=WHITE, width=STROKE - 2)\n d.line([(cx + offset, cy + chip_half), (cx + offset, cy + chip_half + pin_len)],\n fill=WHITE, width=STROKE - 2)\n d.line([(cx - chip_half, cy + offset), (cx - chip_half - pin_len, cy + offset)],\n fill=WHITE, width=STROKE - 2)\n d.line([(cx + chip_half, cy + offset), (cx + chip_half + pin_len, cy + offset)],\n fill=WHITE, width=STROKE - 2)\n return img\n\n\ndef icon_ai_brain():\n \"\"\"Brain with neural nodes — represents Intelligence Age.\"\"\"\n img = Image.new('RGBA', (SIZE, SIZE), TRANSPARENT)\n d = ImageDraw.Draw(img)\n cx, cy = SIZE // 2, SIZE // 2\n\n # Two hemispheres\n d.arc([cx - 68, cy - 52, cx + 2, cy + 52], start=90, end=270, fill=WHITE, width=STROKE)\n d.arc([cx - 2, cy - 52, cx + 68, cy + 52], start=270, end=450, fill=WHITE, width=STROKE)\n\n # Brain wrinkles\n d.arc([cx - 40, cy - 55, cx + 40, cy - 10], start=0, end=180, fill=WHITE, width=STROKE - 2)\n d.arc([cx - 35, cy - 18, cx + 35, cy + 18], start=180, end=360, fill=WHITE, width=STROKE - 2)\n\n # Center line\n d.line([(cx, cy - 52), (cx, cy + 52)], fill=WHITE, width=STROKE - 2)\n\n # Neural nodes\n node_r = 5\n nodes = [(cx - 30, cy - 20), (cx + 30, cy - 20),\n (cx - 20, cy + 18), (cx + 20, cy + 18), (cx, cy)]\n for nx, ny in nodes:\n d.ellipse([nx - node_r, ny - node_r, nx + node_r, ny + node_r], fill=WHITE)\n\n # Connections\n for a, b in [(0, 4), (1, 4), (2, 4), (3, 4), (0, 2), (1, 3)]:\n d.line([nodes[a], nodes[b]], fill=WHITE, width=2)\n return img\n\n\n# ═══════════════════════════════════════════\n# Generate and save all icons\n# ═══════════════════════════════════════════\n\nALL_ICONS = [\n ('icon_person_bust.png', icon_person_bust),\n ('icon_shield_check.png', icon_shield_check),\n ('icon_people_group.png', icon_people_group),\n ('icon_factory_gear.png', icon_factory_gear),\n ('icon_circuit_chip.png', icon_circuit_chip),\n ('icon_ai_brain.png', icon_ai_brain),\n]\n\nif __name__ == '__main__':\n for name, fn in ALL_ICONS:\n path = os.path.join(OUT_DIR, name)\n fn().save(path, 'PNG')\n print(f'Saved: {path}')\n print(f'\\nDone! {len(ALL_ICONS)} icons generated in {OUT_DIR}')","content_type":"text/x-python; charset=utf-8","language":"python","size":8265,"content_sha256":"e821c7a3b5ee680795aca76e2fb370e5ae7a72f0562513f843832cc378df4030"},{"filename":"scripts/minimal_example.py","content":"#!/usr/bin/env python3\n# Copyright 2024-2026 Kaku Li (https://github.com/likaku)\n# Licensed under the Apache License, Version 2.0 — see LICENSE and NOTICE.\n# Part of \"Mck-ppt-design-skill\" (McKinsey PPT Design Framework).\n# NOTICE: This file must be retained in all copies or substantial portions.\n#\n\"\"\"\nMinimal example: McKinsey-style PPT with Cover + Content + Source slides.\nUses the design system defined in SKILL.md (v1.2.0).\n\"\"\"\n\nimport os\nimport shutil\nimport subprocess\nimport zipfile\nfrom lxml import etree\nfrom pptx import Presentation\nfrom pptx.util import Inches, Pt, Emu\nfrom pptx.dml.color import RGBColor\nfrom pptx.enum.text import PP_ALIGN, MSO_ANCHOR\nfrom pptx.enum.shapes import MSO_SHAPE\nfrom pptx.oxml.ns import qn\n\n# ── Color Palette ──\nNAVY = RGBColor(0x05, 0x1C, 0x2C)\nBLACK = RGBColor(0x00, 0x00, 0x00)\nWHITE = RGBColor(0xFF, 0xFF, 0xFF)\nDARK_GRAY = RGBColor(0x33, 0x33, 0x33)\nMED_GRAY = RGBColor(0x66, 0x66, 0x66)\nLINE_GRAY = RGBColor(0xCC, 0xCC, 0xCC)\nBG_GRAY = RGBColor(0xF2, 0xF2, 0xF2)\n\n# ── Font Sizes ──\nTITLE_SIZE = Pt(22)\nBODY_SIZE = Pt(14)\nSUB_HEADER_SIZE = Pt(18)\nHEADER_SIZE = Pt(28)\nSMALL_SIZE = Pt(9)\n\n# ── Helper Functions ──\n\ndef _clean_shape(shape):\n \"\"\"Remove p:style from any shape to prevent effect references.\"\"\"\n sp = shape._element\n style = sp.find(qn('p:style'))\n if style is not None:\n sp.remove(style)\n\ndef set_ea_font(run, typeface='KaiTi'):\n rPr = run._r.get_or_add_rPr()\n ea = rPr.find(qn('a:ea'))\n if ea is None:\n ea = rPr.makeelement(qn('a:ea'), {})\n rPr.append(ea)\n ea.set('typeface', typeface)\n\ndef add_text(slide, left, top, width, height, text, font_size=Pt(14),\n font_name='Arial', font_color=DARK_GRAY, bold=False,\n alignment=PP_ALIGN.LEFT, ea_font='KaiTi', anchor=MSO_ANCHOR.TOP):\n txBox = slide.shapes.add_textbox(left, top, width, height)\n tf = txBox.text_frame\n tf.word_wrap = True\n tf.auto_size = None\n bodyPr = tf._txBody.find(qn('a:bodyPr'))\n anchor_map = {MSO_ANCHOR.MIDDLE: 'ctr', MSO_ANCHOR.BOTTOM: 'b', MSO_ANCHOR.TOP: 't'}\n bodyPr.set('anchor', anchor_map.get(anchor, 't'))\n for attr in ['lIns', 'tIns', 'rIns', 'bIns']:\n bodyPr.set(attr, '45720')\n p = tf.paragraphs[0]\n p.text = text\n p.font.size = font_size\n p.font.name = font_name\n p.font.color.rgb = font_color\n p.font.bold = bold\n p.alignment = alignment\n p.space_before = Pt(0)\n p.space_after = Pt(0)\n for run in p.runs:\n set_ea_font(run, ea_font)\n return txBox\n\ndef add_rect(slide, left, top, width, height, fill_color):\n shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, left, top, width, height)\n shape.fill.solid()\n shape.fill.fore_color.rgb = fill_color\n shape.line.fill.background()\n _clean_shape(shape) # CRITICAL: remove p:style\n return shape\n\ndef add_hline(slide, x, y, length, color=BLACK, thickness=Pt(0.5)):\n \"\"\"Draw a horizontal line using a thin rectangle (no connector).\"\"\"\n h = max(int(thickness), Emu(6350)) # minimum ~0.5pt\n return add_rect(slide, x, y, length, h, color)\n\ndef add_oval(slide, x, y, letter, size=Inches(0.45),\n bg=NAVY, fg=WHITE):\n \"\"\"Add a circle label with a letter (e.g. 'A', '1').\n Uses Arial font to match body text consistency.\"\"\"\n c = slide.shapes.add_shape(MSO_SHAPE.OVAL, x, y, size, size)\n c.fill.solid()\n c.fill.fore_color.rgb = bg\n c.line.fill.background()\n tf = c.text_frame\n tf.paragraphs[0].text = letter\n tf.paragraphs[0].font.size = Pt(14)\n tf.paragraphs[0].font.name = 'Arial'\n tf.paragraphs[0].font.color.rgb = fg\n tf.paragraphs[0].font.bold = True\n tf.paragraphs[0].alignment = PP_ALIGN.CENTER\n for run in tf.paragraphs[0].runs:\n set_ea_font(run, 'KaiTi')\n bodyPr = tf._txBody.find(qn('a:bodyPr'))\n bodyPr.set('anchor', 'ctr')\n _clean_shape(c) # CRITICAL: remove p:style\n return c\n\ndef add_action_title(slide, text, title_size=Pt(22)):\n add_text(slide, Inches(0.8), Inches(0.15), Inches(11.7), Inches(0.9),\n text, font_size=title_size, font_color=BLACK, bold=True,\n font_name='Georgia', ea_font='KaiTi', anchor=MSO_ANCHOR.MIDDLE)\n add_hline(slide, Inches(0.8), Inches(1.05), Inches(11.7),\n color=BLACK, thickness=Pt(0.5))\n\ndef add_source(slide, text, y=Inches(7.05)):\n add_text(slide, Inches(0.8), y, Inches(11), Inches(0.3),\n text, font_size=Pt(9), font_color=MED_GRAY)\n\ndef full_cleanup(outpath):\n \"\"\"Remove ALL p:style from every slide + theme shadows/3D.\"\"\"\n tmppath = outpath + '.tmp'\n with zipfile.ZipFile(outpath, 'r') as zin:\n with zipfile.ZipFile(tmppath, 'w', zipfile.ZIP_DEFLATED) as zout:\n for item in zin.infolist():\n data = zin.read(item.filename)\n if item.filename.endswith('.xml'):\n root = etree.fromstring(data)\n ns_p = 'http://schemas.openxmlformats.org/presentationml/2006/main'\n ns_a = 'http://schemas.openxmlformats.org/drawingml/2006/main'\n # Remove ALL p:style elements from all shapes/connectors\n for style in root.findall(f'.//{{{ns_p}}}style'):\n style.getparent().remove(style)\n # Remove shadows and 3D from theme\n if 'theme' in item.filename.lower():\n for tag in ['outerShdw', 'innerShdw', 'scene3d', 'sp3d']:\n for el in root.findall(f'.//{{{ns_a}}}{tag}'):\n el.getparent().remove(el)\n data = etree.tostring(root, xml_declaration=True,\n encoding='UTF-8', standalone=True)\n zout.writestr(item, data)\n os.replace(tmppath, outpath)\n\ndef deliver_to_channel(outpath, slide_count):\n \"\"\"Send generated PPTX back to user's chat channel via OpenClaw media pipeline.\n Falls back gracefully if not running in a channel context.\"\"\"\n if not shutil.which('openclaw'):\n print(f'[deliver] openclaw CLI not found, skipping channel delivery')\n print(f'[deliver] File saved locally: {outpath}')\n return False\n\n size_kb = os.path.getsize(outpath) / 1024\n caption = f'✅ PPT generated — {slide_count} slides, {size_kb:.0f} KB'\n\n try:\n result = subprocess.run(\n ['openclaw', 'message', 'send',\n '--media', outpath,\n '--message', caption],\n capture_output=True, text=True, timeout=30\n )\n if result.returncode == 0:\n print(f'[deliver] Sent to channel: {outpath}')\n return True\n else:\n print(f'[deliver] Channel send failed: {result.stderr}')\n print(f'[deliver] File saved locally: {outpath}')\n return False\n except Exception as e:\n print(f'[deliver] Error: {e}')\n print(f'[deliver] File saved locally: {outpath}')\n return False\n\n# ── Build Presentation ──\n\ndef main():\n prs = Presentation()\n prs.slide_width = Inches(13.333)\n prs.slide_height = Inches(7.5)\n blank = prs.slide_layouts[6]\n\n # Slide 1: Cover\n s1 = prs.slides.add_slide(blank)\n add_rect(s1, 0, 0, prs.slide_width, Inches(0.05), NAVY)\n add_text(s1, Inches(1), Inches(2.2), Inches(11), Inches(1.0),\n 'Sample Presentation', font_size=Pt(44), font_name='Georgia',\n font_color=NAVY, bold=True)\n add_text(s1, Inches(1), Inches(3.5), Inches(11), Inches(0.6),\n 'McKinsey-style Design System Demo', font_size=Pt(24),\n font_color=DARK_GRAY)\n add_text(s1, Inches(1), Inches(4.5), Inches(11), Inches(0.5),\n 'Minimal Example | 2026', font_size=BODY_SIZE, font_color=MED_GRAY)\n add_hline(s1, Inches(1), Inches(6.8), Inches(3), color=NAVY, thickness=Pt(2))\n\n # Slide 2: Content\n s2 = prs.slides.add_slide(blank)\n add_action_title(s2, 'Key Findings Overview')\n items = [\n 'Clean typography hierarchy ensures readability',\n 'Flat design with no shadows or 3D effects',\n 'Consistent color palette across all slides',\n 'Proper East Asian font handling for Chinese text',\n ]\n for i, item in enumerate(items):\n y = Inches(1.6) + Inches(0.6) * i\n add_oval(s2, Inches(0.9), y, str(i + 1))\n add_text(s2, Inches(1.5), y, Inches(10), Inches(0.5), item)\n if i \u003c len(items) - 1:\n add_hline(s2, Inches(0.9), y + Inches(0.55), Inches(11.3), LINE_GRAY)\n add_source(s2, 'Source: Mck-ppt-design-skill v1.2.0')\n\n # Save & cleanup & deliver\n outpath = 'minimal_output.pptx'\n prs.save(outpath)\n full_cleanup(outpath)\n deliver_to_channel(outpath, len(prs.slides))\n print(f'Created: {outpath} ({os.path.getsize(outpath):,} bytes)')\n\nif __name__ == '__main__':\n main()","content_type":"text/x-python; charset=utf-8","language":"python","size":8923,"content_sha256":"019437c1ef47f45560acdbcfed557328b847bb143e003f51091ccdeb55ec8fd2"},{"filename":"scripts/requirements.txt","content":"python-pptx>=0.6.21\nlxml>=4.9.0\nPillow>=9.0\nnumpy>=1.24\nrembg>=2.0\ntencentcloud-sdk-python>=3.0\n","content_type":"text/plain; charset=utf-8","language":null,"size":96,"content_sha256":"dd4e35499eceb4b7a6a2c72da1215277b37597574b1f48660623a9c534d66e79"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"McKinsey PPT Design — Harness Framework","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Version","type":"text","marks":[{"type":"strong"}]},{"text":": 2.3.3-harness-v2 · ","type":"text"},{"text":"Engine","type":"text","marks":[{"type":"strong"}]},{"text":": MckEngine (python-pptx) · ","type":"text"},{"text":"Author","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"likaku","type":"text","marks":[{"type":"link","attrs":{"href":"https://github.com/likaku/Mck-ppt-design-skill","title":null}}]}]},{"type":"paragraph","content":[{"text":"Required tools","type":"text","marks":[{"type":"strong"}]},{"text":": Read, Write, Bash · ","type":"text"},{"text":"Requires","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"pip install python-pptx lxml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"常见崩坏模式(每次生成前必读,先于 HARD RULES)","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"告诉 AI 要做什么效果一般,告诉它前人最容易在哪里塌效果更好。 以下三种反模式均已在真实执行中被验证,每次必须主动警惕。","type":"text"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"反模式 1:口头宣布\"门禁通过\"(最常见)","type":"text"}]},{"type":"paragraph","content":[{"text":"错误做法","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"「S4 QA 共 7 个 errors,判断均为 engine 设计行为,门禁通过,进入 S5」","type":"text"}]}]},{"type":"paragraph","content":[{"text":"问题所在","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"passed","type":"text","marks":[{"type":"code_inline"}]},{"text":" 是由 AI 口头判断的,不是由程序派生的。无论理由多充分,这句话都是 AI 在给自己写完成证书。","type":"text"}]},{"type":"paragraph","content":[{"text":"正确做法","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"执行 ","type":"text"},{"text":"python references/scripts/gate_check.py \u003cpptx路径> \u003c项目目录>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"读取 ","type":"text"},{"text":"\u003c项目目录>/gate_result.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"只有 ","type":"text"},{"text":"gate_result.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中 ","type":"text"},{"text":"\"passed\": true","type":"text","marks":[{"type":"code_inline"}]},{"text":" 时,才能进入 S5","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"如果 ","type":"text"},{"text":"\"passed\": false","type":"text","marks":[{"type":"code_inline"}]},{"text":",修复 ","type":"text"},{"text":"user_code_errors","type":"text","marks":[{"type":"code_inline"}]},{"text":" 列表中的问题,重新渲染,再次执行 gate_check","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"反模式 2:S3 门禁\"脑子里过一遍\"就算通过","type":"text"}]},{"type":"paragraph","content":[{"text":"错误做法","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"「S3 内容门禁预检:API 格式正确,字数在限制内,通过,进入 S4」(没有执行任何代码)","type":"text"}]}]},{"type":"paragraph","content":[{"text":"问题所在","type":"text","marks":[{"type":"strong"}]},{"text":":今天真实发生的 3 个 API 格式错误(","type":"text"},{"text":"four_column","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"matrix_2x2","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"executive_summary","type":"text","marks":[{"type":"code_inline"}]},{"text":" 参数格式),靠脑子过是过不出来的,必须靠代码检查。","type":"text"}]},{"type":"paragraph","content":[{"text":"正确做法","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"执行 ","type":"text"},{"text":"python references/scripts/gate_check_s3.py \u003ccontent.json路径> \u003c项目目录>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"读取 ","type":"text"},{"text":"\u003c项目目录>/gate_s3.json","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"只有 ","type":"text"},{"text":"\"passed\": true","type":"text","marks":[{"type":"code_inline"}]},{"text":" 时,才能进入 S4","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"如果有 ","type":"text"},{"text":"fail","type":"text","marks":[{"type":"code_inline"}]},{"text":" 项,修正 ","type":"text"},{"text":"content.json","type":"text","marks":[{"type":"code_inline"}]},{"text":",重新执行 gate_check_s3","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"反模式 3:engine_bug 分类作为软话逃生口","type":"text"}]},{"type":"paragraph","content":[{"text":"错误做法","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"「peer_font_inconsistency 是 engine 内部设计行为,不是用户代码问题,可以放行」","type":"text"}]}]},{"type":"paragraph","content":[{"text":"问题所在","type":"text","marks":[{"type":"strong"}]},{"text":":这个分类本身是正确的,但由 AI 在对话里口头做出,等于把豁免权交给了 AI 自己——而 AI 有动机让自己通过。","type":"text"}]},{"type":"paragraph","content":[{"text":"正确做法","type":"text","marks":[{"type":"strong"}]},{"text":": ","type":"text"},{"text":"gate_check.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 里有硬编码的 ","type":"text"},{"text":"ENGINE_BUG_WHITELIST","type":"text","marks":[{"type":"code_inline"}]},{"text":" 枚举。只有在白名单里的 error category,才会被豁免。白名单是代码,不是 AI 的判断。如果你认为某类 error 应该加入白名单,修改 ","type":"text"},{"text":"gate_check.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" 里的枚举,而不是口头声明豁免。","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"HARD RULES(必须遵守,不可绕过)","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"每次生成必须走五阶段流程","type":"text","marks":[{"type":"strong"}]},{"text":",不允许\"一句话直接生成\"跳过前置阶段","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TaskCreate 驱动进度","type":"text","marks":[{"type":"strong"}]},{"text":":开始前创建5个 task,每阶段完成后立即 TaskUpdate completed","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"按需加载上下文","type":"text","marks":[{"type":"strong"}]},{"text":":每个阶段只读对应文件,不要全量加载旧版 SKILL.md 里的内容","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"门禁必须机读化","type":"text","marks":[{"type":"strong"}]},{"text":":S3 和 S4 门禁必须执行对应的 gate_check 脚本,读 JSON 结果,不得口头判断","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Self-Refinement 必做","type":"text","marks":[{"type":"strong"}]},{"text":":每次修正 pattern-level 问题后,写入 ","type":"text"},{"text":"experiences/","type":"text","marks":[{"type":"code_inline"}]},{"text":" 对应文件","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"引擎路径固定","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"sys.path.insert(0, os.path.expanduser('~/.workbuddy/skills/mck-ppt-design'))","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"知识路由(上下文加载索引)","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"在每个阶段开始时,读对应文件","type":"text","marks":[{"type":"strong"}]},{"text":"。不要一次性全读。","type":"text"}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"阶段","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"必须读","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"说明","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S1 需求","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/team/brand-guide.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"了解设计约束","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S2 结构","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/framework/engine-api.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"references/layout-matrix.yaml","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"版式选择和能力边界","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S3 内容","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"references/framework/guard-rails.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"experiences/*.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"(存在时)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"防溢出规则 + 历史踩坑","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S4 渲染","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"用到的 ","type":"text"},{"text":"references/layouts/*.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"(只读实际用到的版式)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"版式实现细节","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"S5 交付","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"无","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"—","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"完整路由表","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/INDEX.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"五阶段生成流程","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"┌─────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐ ┌──────────┐\n│ S1 需求 │──▶│ S2 结构 ⭐ │──▶│ S3 内容 ⭐ │──▶│ S4 渲染+QA ⭐⭐│──▶│ S5 交付 │\n│ brief.md│ │ outline.json│ │content.json │ │ .pptx │ │ + 沉淀 │\n└─────────┘ └─────────────┘ └─────────────┘ └──────────────┘ └──────────┘\n ⭐ = 门禁(FAIL 则在本阶段修正,不得跳过)\n S3/S4 门禁必须运行 gate_check 脚本,读 JSON 结果 — 不得口头宣布通过","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 1: 需求定义","type":"text"}]},{"type":"paragraph","content":[{"text":"读文件","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/team/brand-guide.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"收集信息","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"受众(职位/决策角色)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"目标(决策/汇报/说服)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"时长(分钟数 → 约 1 分钟/页)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"关键信息(最多 5 条核心 message)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"数据来源(有哪些数据可用)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"产出","type":"text","marks":[{"type":"strong"}]},{"text":":在工作目录创建 ","type":"text"},{"text":"ppt-project-{slug}/brief.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"门禁","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"audience","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"goal","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"key_messages","type":"text","marks":[{"type":"code_inline"}]},{"text":" 三项非空(AI 自评即可)","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 2: 结构设计","type":"text"}]},{"type":"paragraph","content":[{"text":"读文件","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/framework/engine-api.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"references/layout-matrix.yaml","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"任务","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"根据时长确定页数(1分钟/页)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"为每张幻灯片选定 ","type":"text"},{"text":"layout","type":"text","marks":[{"type":"code_inline"}]},{"text":"(查 engine-api.md 速查表)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"每页写一句核心 ","type":"text"},{"text":"key_point","type":"text","marks":[{"type":"code_inline"}]},{"text":"(完整句子,不是标签)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"确认版式在能力边界内(查 layout-matrix.yaml)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"产出","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"ppt-project-{slug}/outline.json","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"json"},"content":[{"text":"{\n \"brief\": {\"audience\": \"董事会\", \"goal\": \"战略汇报\", \"duration_minutes\": 15},\n \"slides\": [\n {\"idx\": 1, \"layout\": \"cover\", \"title\": \"Q1 2026 战略回顾\", \"key_point\": \"\"},\n {\"idx\": 2, \"layout\": \"toc\", \"title\": \"目录\", \"key_point\": \"\"},\n {\"idx\": 3, \"layout\": \"table_insight\", \"title\": \"市场格局三大转变驱动战略重构\", \"key_point\": \"\"}\n ]\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"⭐ 门禁 S2","type":"text","marks":[{"type":"strong"}]},{"text":"(AI 自评):","type":"text"}]},{"type":"checkbox_list","attrs":{"id":null},"content":[{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"cover","type":"text","marks":[{"type":"code_inline"}]},{"text":" 幻灯片存在","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"幻灯片数量在时长约束内(","type":"text"},{"text":"count \u003c= duration_minutes * 1.2","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"所有 layout 在 ","type":"text"},{"text":"layout-matrix.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中有定义","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"Action Title 均为完整句子(len > 10,包含动词)","type":"text"}]}]},{"type":"checkbox_item","attrs":{"checked":false},"content":[{"type":"paragraph","content":[{"text":"two_column_text","type":"text","marks":[{"type":"code_inline"}]},{"text":" 数量 ≤ 1","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 3: 内容填充","type":"text"}]},{"type":"paragraph","content":[{"text":"读文件","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/framework/guard-rails.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"experiences/*.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"(全部存在的文件)","type":"text"}]},{"type":"paragraph","content":[{"text":"任务","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"为每张幻灯片填充具体文案、数字、图表数据","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"确保每页有 ","type":"text"},{"text":"source","type":"text","marks":[{"type":"code_inline"}]},{"text":" 出处","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"按 ","type":"text"},{"text":"layout-matrix.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" 的 ","type":"text"},{"text":"char_budget","type":"text","marks":[{"type":"code_inline"}]},{"text":" 控制文字量","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"产出","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"ppt-project-{slug}/content.json","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"⭐ 门禁 S3","type":"text","marks":[{"type":"strong"}]},{"text":"(必须机读化,不得 AI 自评):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.workbuddy/skills/mck-ppt-design/references/scripts/gate_check_s3.py \\\n \u003c项目目录>/content.json \u003c项目目录>","type":"text"}]},{"type":"paragraph","content":[{"text":"读取 ","type":"text"},{"text":"\u003c项目目录>/gate_s3.json","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"passed\": true","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 进入 S4","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"passed\": false","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 修正 ","type":"text"},{"text":"content.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 中 ","type":"text"},{"text":"fail_items","type":"text","marks":[{"type":"code_inline"}]},{"text":" 列出的问题,重新执行","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 4: 渲染 + QA","type":"text"}]},{"type":"paragraph","content":[{"text":"读文件","type":"text","marks":[{"type":"strong"}]},{"text":":用到的版式对应的 ","type":"text"},{"text":"references/layouts/*.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"任务","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"根据 ","type":"text"},{"text":"content.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 生成 Python 渲染脚本","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"执行脚本生成 ","type":"text"},{"text":".pptx","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"运行 QA 门禁脚本","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"⭐⭐ 门禁 S4","type":"text","marks":[{"type":"strong"}]},{"text":"(必须机读化,不得口头宣布通过):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python ~/.workbuddy/skills/mck-ppt-design/references/scripts/gate_check.py \\\n \u003cpptx路径> \u003c项目目录>","type":"text"}]},{"type":"paragraph","content":[{"text":"读取 ","type":"text"},{"text":"\u003c项目目录>/gate_result.json","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"passed\": true","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 进入 S5","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"passed\": false","type":"text","marks":[{"type":"code_inline"}]},{"text":" → 查看 ","type":"text"},{"text":"user_code_errors","type":"text","marks":[{"type":"code_inline"}]},{"text":",修复渲染代码,重新渲染,再次执行","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"注意","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"engine_bug","type":"text","marks":[{"type":"code_inline"}]},{"text":" 类 errors(","type":"text"},{"text":"peer_font_inconsistency","type":"text","marks":[{"type":"code_inline"}]},{"text":" 等白名单条目)由脚本自动豁免,不需要 AI 口头判断。","type":"text"}]},{"type":"paragraph","content":[{"text":"产出","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"\u003c项目目录>/gate_result.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":".pptx","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":3},"content":[{"text":"Stage 5: 交付 + Self-Refinement","type":"text"}]},{"type":"paragraph","content":[{"text":"任务","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"读取 ","type":"text"},{"text":"gate_result.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" 确认 ","type":"text"},{"text":"passed: true","type":"text","marks":[{"type":"code_inline"}]},{"text":"(不得在无此文件时声称通过)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"交付 ","type":"text"},{"text":".pptx","type":"text","marks":[{"type":"code_inline"}]},{"text":" 文件","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Self-Refinement","type":"text","marks":[{"type":"strong"}]},{"text":":判断本次是否有 pattern-level 修正","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"⭐ Self-Refinement 协议","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"修正后判断:\n ONE-TIME(单次特定调整)→ 不需要沉淀\n PATTERN(跨 deck 可重现的问题)→ 必须写入 experiences/\n - 溢出类 → experiences/overflow.md\n - 图表限制 → experiences/chart-limits.md\n - 版式踩坑 → experiences/layout-pitfalls.md\n - 中文渲染 → experiences/cjk-issues.md\n\n格式:\n ## Experience NNN: {title}\n **Date**: YYYY-MM-DD\n **Problem**: ...\n **Root Cause**: ...\n **Fix**: ...\n **Rule**: ...(门禁层面如何预防)","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Fast Track(简单需求跳过 S2/S3 门禁)","type":"text"}]},{"type":"paragraph","content":[{"text":"满足","type":"text"},{"text":"全部","type":"text","marks":[{"type":"strong"}]},{"text":"条件时,AI 可跳过 S2 结构评审和 S3 内容审查:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"总页数 ≤ 5","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"无数据图表(donut/bar/table 类)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"用户明确表达\"快速\"/\"马上要\"/\"简单\"","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"仍然必须","type":"text","marks":[{"type":"strong"}]},{"text":":S1 需求收集 + S4 QA 门禁(gate_check.py 脚本执行)+ S5 交付","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Checkpoint / 断点恢复","type":"text"}]},{"type":"paragraph","content":[{"text":"当用户说\"继续做那个 PPT\"时:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"import os, json, glob\n\nprojects = glob.glob('ppt-project-*/')\nfor proj in projects:\n brief = os.path.exists(f'{proj}brief.md')\n outline = os.path.exists(f'{proj}outline.json')\n content = os.path.exists(f'{proj}content.json')\n gate_s3 = os.path.exists(f'{proj}gate_s3.json')\n gate_s4 = os.path.exists(f'{proj}gate_result.json')\n pptx = bool(glob.glob(f'{proj}*.pptx'))\n\n if not brief: stage = 1\n elif not outline: stage = 2\n elif not content: stage = 3\n elif not gate_s3: stage = '3-gate' # content 有了但 gate 还没跑\n elif not pptx: stage = 4\n elif not gate_s4: stage = '4-gate' # pptx 有了但 gate 还没跑\n else: stage = 5\n\n print(f\"项目 {proj}: 当前处于 Stage {stage}\")","type":"text"}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"MckEngine 快速模板","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"python"},"content":[{"text":"import sys, os\nsys.path.insert(0, os.path.expanduser('~/.workbuddy/skills/mck-ppt-design'))\nfrom mck_ppt import MckEngine\nfrom mck_ppt.constants import *\n\neng = MckEngine(total_slides=12)\neng.cover(title='标题', subtitle='副标题', author='作者', date='2026年')\neng.toc(items=[('1', '章节一', '一句话描述'), ('2', '章节二', '一句话描述')])\n# ... 内容页 ...\neng.closing(title='谢谢', message='期待进一步交流')\neng.save('output/deck.pptx') # 自动 full_cleanup","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"详细 API","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/framework/engine-api.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"版式规范","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/layouts/","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"10条护栏","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/framework/guard-rails.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"版式能力边界","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/layout-matrix.yaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"历史踩坑","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"experiences/","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"S3 门禁脚本","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/scripts/gate_check_s3.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" ","type":"text"},{"text":"S4 门禁脚本","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"},{"text":"references/scripts/gate_check.py","type":"text","marks":[{"type":"code_inline"}]}]}]},"metadata":{"date":"2026-06-05","name":"mck-ppt-design","author":"@skillopedia","source":{"stars":161,"repo_name":"mck-ppt-design-skill","origin_url":"https://github.com/likaku/mck-ppt-design-skill/blob/HEAD/SKILL.md","repo_owner":"likaku","body_sha256":"39043c347eb51fe7bdd3c2330c159280ee2fff96325bc354ca9de299f0082fb5","cluster_key":"bef571910421996ebb1c1ee53107589dea7664d92da2b4210dc0ede49e2fb514","clean_bundle":{"format":"clean-skill-bundle-v1","source":"likaku/mck-ppt-design-skill/SKILL.md","attachments":[{"id":"aee4ca1d-d3ef-5601-8c26-63249f785f31","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/aee4ca1d-d3ef-5601-8c26-63249f785f31/attachment","path":".gitignore","size":585,"sha256":"7ef03614eb054372370002b95c847fef7638b389dd6edaa28d7ff09107f0c7e9","contentType":"text/plain; charset=utf-8"},{"id":"de2c4849-89c0-5180-9130-182b073dd2e9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/de2c4849-89c0-5180-9130-182b073dd2e9/attachment.md","path":"CHANGELOG.md","size":24799,"sha256":"6eca9657be5799a867cc432993425828a9949fbe2fe9d50b213ac3844438a6c2","contentType":"text/markdown; charset=utf-8"},{"id":"3889fda8-1bd4-52b1-9803-1000c2b3ff4e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3889fda8-1bd4-52b1-9803-1000c2b3ff4e/attachment","path":"NOTICE","size":1510,"sha256":"eb9636ea5c057a04be7ba609803e04e7c91f70c454b128839eb1c01f720caa33","contentType":"text/plain; charset=utf-8"},{"id":"4dcde4c2-f8ae-555f-8107-35115ded8e12","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4dcde4c2-f8ae-555f-8107-35115ded8e12/attachment.md","path":"README.md","size":21362,"sha256":"f2058627f43aaccb54f96391e27f5f0a7847e04f770f3598ca316829ad754692","contentType":"text/markdown; charset=utf-8"},{"id":"cb30fd39-ae43-56b0-9c92-d55ff8b14edd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cb30fd39-ae43-56b0-9c92-d55ff8b14edd/attachment.png","path":"assets/icons/icon_ai_brain.png","size":1496,"sha256":"c5b2249baa709d880187d4c20c5daf56b738a8b4988888a25418ea503b1d531d","contentType":"image/png"},{"id":"862100af-d667-5fb8-a746-8dc8a57fc6f6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/862100af-d667-5fb8-a746-8dc8a57fc6f6/attachment.png","path":"assets/icons/icon_circuit_chip.png","size":696,"sha256":"00e96add54c776f15ae4d0c41249c0530ff2a4477487a432116e734f75646a0e","contentType":"image/png"},{"id":"fe6f75b3-4ad0-5fe8-b4b0-d1a4763fc66f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fe6f75b3-4ad0-5fe8-b4b0-d1a4763fc66f/attachment.png","path":"assets/icons/icon_factory_gear.png","size":1785,"sha256":"1b56abc11807381a98071c2fca50c87f677058be1a45fe22ae125eaa7156d805","contentType":"image/png"},{"id":"16e4f154-fd1e-5e4c-ac15-7840c95c92cb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/16e4f154-fd1e-5e4c-ac15-7840c95c92cb/attachment.png","path":"assets/icons/icon_people_group.png","size":1318,"sha256":"298aa9bc04f9b9eb4e3dd96542b0a6100d192d8720ea9fd86c05509d81600de6","contentType":"image/png"},{"id":"79a542a5-cecf-510a-b758-464fd5b5724a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/79a542a5-cecf-510a-b758-464fd5b5724a/attachment.png","path":"assets/icons/icon_person_bust.png","size":1111,"sha256":"1a804245093715469eb9fc71993845b58b9050461986ed99333e346820d11461","contentType":"image/png"},{"id":"0612bfbe-f79f-56bf-8039-f01f329338ea","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0612bfbe-f79f-56bf-8039-f01f329338ea/attachment.png","path":"assets/icons/icon_shield_check.png","size":1147,"sha256":"e6926f345d3ed2addfdd3b4677ab72b4104cdf7c49c7e04d2cfe044a60b741ed","contentType":"image/png"},{"id":"f14dd433-2637-5add-a576-0f4d9089db90","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f14dd433-2637-5add-a576-0f4d9089db90/attachment.png","path":"docs/colors/bg-gray.png","size":131,"sha256":"defd5599b0d1ece7008994d2bad9fcb72f5d1ad2b2162a6fc72988f229ebb26c","contentType":"image/png"},{"id":"3cd47c24-422f-5056-b3f4-99a498eb1d2a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3cd47c24-422f-5056-b3f4-99a498eb1d2a/attachment.png","path":"docs/colors/black.png","size":88,"sha256":"238132fa553e96c57bf322c6d3c913fea7df4f29b939c555fe040a43b46c1657","contentType":"image/png"},{"id":"4fba9efb-1ef4-5ed9-8d7a-0a287fb7d19f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4fba9efb-1ef4-5ed9-8d7a-0a287fb7d19f/attachment.png","path":"docs/colors/dark-gray.png","size":131,"sha256":"3c1939951f62f36d1ef68807e0c018f10bdb85faf1d85a56dc66636c44210325","contentType":"image/png"},{"id":"072d3f4a-d6d4-5096-8525-61f9a319a935","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/072d3f4a-d6d4-5096-8525-61f9a319a935/attachment.png","path":"docs/colors/line-gray.png","size":131,"sha256":"6ee4caa5d2eec38ad62e887e3ed47d3e8c0d84b5e90b69b423a66f268cee425f","contentType":"image/png"},{"id":"7c5698fb-70d3-5ad4-a058-20b6e91d5ae3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7c5698fb-70d3-5ad4-a058-20b6e91d5ae3/attachment.png","path":"docs/colors/med-gray.png","size":131,"sha256":"35c54fc1b8944abbcf24748d0d1d7e1ad5138e6a0ce8b5264f00e13fba5ff2a5","contentType":"image/png"},{"id":"79fdd596-998f-5f2f-94cf-ca45cfe12c74","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/79fdd596-998f-5f2f-94cf-ca45cfe12c74/attachment.png","path":"docs/colors/navy.png","size":134,"sha256":"0c5a7d47d7795fdea3d6bc15bb81f4882b5b59c8b6c44668471c1d1c6d8ebe2e","contentType":"image/png"},{"id":"503f5ed0-7a0c-5ea9-8c4b-757fbc1f742e","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/503f5ed0-7a0c-5ea9-8c4b-757fbc1f742e/attachment","path":"docs/screenshots/.gitkeep","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/plain; charset=utf-8"},{"id":"33b9a5fd-3b2c-564e-b84d-3d9200576dbf","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/33b9a5fd-3b2c-564e-b84d-3d9200576dbf/attachment.placeholder","path":"docs/wechat-qr.png.placeholder","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/plain; charset=utf-8"},{"id":"2fd2fc74-b23d-50d9-b166-775e67ac5cc3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2fd2fc74-b23d-50d9-b166-775e67ac5cc3/attachment.pptx","path":"examples/civilization_staircase.pptx","size":34390,"sha256":"f516b7056f5464cf77140967717e8a20e328d3df90b87b6006450e8265d390f8","contentType":"application/vnd.openxmlformats-officedocument.presentationml.presentation"},{"id":"eef073ca-9d8a-5b3f-b558-0af0f2f5caf1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/eef073ca-9d8a-5b3f-b558-0af0f2f5caf1/attachment.py","path":"examples/minimal_example.py","size":8923,"sha256":"019437c1ef47f45560acdbcfed557328b847bb143e003f51091ccdeb55ec8fd2","contentType":"text/x-python; charset=utf-8"},{"id":"9ceeda0b-6d5a-5fb8-a4e4-42e3d27a9763","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9ceeda0b-6d5a-5fb8-a4e4-42e3d27a9763/attachment.txt","path":"examples/requirements.txt","size":98,"sha256":"53198f63ebf8ed0f4866960f32291319b354a34cbfab0c5a781eee5637cdc6f1","contentType":"text/plain; charset=utf-8"},{"id":"79baa3d8-e099-522b-8ba1-e73c2edcee9c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/79baa3d8-e099-522b-8ba1-e73c2edcee9c/attachment.py","path":"examples/staircase_civilization.py","size":3935,"sha256":"b81d32d2fbd4ec72fb690fb0978d5148da08ad42a56d9eb95d8dc5a160397263","contentType":"text/x-python; charset=utf-8"},{"id":"f6cabc71-8ab7-54d5-9cec-784ef6e8a5ec","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f6cabc71-8ab7-54d5-9cec-784ef6e8a5ec/attachment.md","path":"experiences/chart-limits.md","size":1175,"sha256":"bac8caabfd70ef3ddb3ab6bf3caf5cb4ce2fb5ece4579f6f1734b9d13ff0af99","contentType":"text/markdown; charset=utf-8"},{"id":"b1f89f66-e557-5e0e-a129-4266fadfd9ff","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b1f89f66-e557-5e0e-a129-4266fadfd9ff/attachment.md","path":"experiences/cjk-issues.md","size":1254,"sha256":"c9ff758a8f684e195645e1f9d7f367bc1a8c0ccd2c8d4add44560cf1e2e09df9","contentType":"text/markdown; charset=utf-8"},{"id":"2a29dc6d-5f6c-5c24-839b-ac02b1931766","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2a29dc6d-5f6c-5c24-839b-ac02b1931766/attachment.md","path":"experiences/layout-pitfalls.md","size":2194,"sha256":"85f432249f4e2cb87fa793929b9d1a9c6e933f291613ebf3379e6d3db4d20431","contentType":"text/markdown; charset=utf-8"},{"id":"2f9ce8ba-f90c-5d8e-84bd-504af5f14a8f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2f9ce8ba-f90c-5d8e-84bd-504af5f14a8f/attachment.md","path":"experiences/overflow.md","size":2705,"sha256":"5d7d689233d1f102865fd4100e8c1fc40418e15e6e25afb5d87e2934000df8a1","contentType":"text/markdown; charset=utf-8"},{"id":"cd15979b-d6b0-5a2e-923b-08e0f0a34113","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/cd15979b-d6b0-5a2e-923b-08e0f0a34113/attachment.py","path":"mck_ppt/__init__.py","size":893,"sha256":"66ea258f6a0a9978a3ea95dda5bdf9892f0dc2a400440d62f8f102e00e914ce7","contentType":"text/x-python; charset=utf-8"},{"id":"5acc62ea-cbf9-5a96-ba04-a96e65525106","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5acc62ea-cbf9-5a96-ba04-a96e65525106/attachment.py","path":"mck_ppt/constants.py","size":3437,"sha256":"3f032eba51335a93b254ad074ea670196773261fc522aa2f9e5be3129bbd563c","contentType":"text/x-python; charset=utf-8"},{"id":"c2738513-a601-5aa8-a365-a6891d4b5125","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c2738513-a601-5aa8-a365-a6891d4b5125/attachment.py","path":"mck_ppt/core.py","size":12888,"sha256":"dd1c6526e39d37983680ae91c05ecef350e198da77774671f28862c7d7e98789","contentType":"text/x-python; charset=utf-8"},{"id":"b6a48769-36e5-5960-adc7-36e9916b5e44","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b6a48769-36e5-5960-adc7-36e9916b5e44/attachment.py","path":"mck_ppt/cover_image.py","size":12833,"sha256":"2b4f3e41fb5f5a963d89ba09dce5713b4ddef90627a99309feda1108fd244d9b","contentType":"text/x-python; charset=utf-8"},{"id":"708aab16-4047-5e57-a6b4-37a689ea0489","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/708aab16-4047-5e57-a6b4-37a689ea0489/attachment.py","path":"mck_ppt/deck_builder.py","size":5886,"sha256":"cbc99c739c3cd98fba162a07f8a9ee3a251c387c1fea2c6be2f20c6d9136435a","contentType":"text/x-python; charset=utf-8"},{"id":"a0ad80ec-8d8c-5ac4-a7a0-02194b327407","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a0ad80ec-8d8c-5ac4-a7a0-02194b327407/attachment.py","path":"mck_ppt/engine.py","size":166915,"sha256":"9b59a9350dee96ed814615352534a89cf7c978b10e37ec184f928c7adbdf2c97","contentType":"text/x-python; charset=utf-8"},{"id":"77d439ee-d3cd-5496-ba60-efd52551ffbc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/77d439ee-d3cd-5496-ba60-efd52551ffbc/attachment.py","path":"mck_ppt/qa.py","size":38914,"sha256":"f802601fcf272ce500de56996fb527ae89280a76823f9d1aff5afc4a8297eb1d","contentType":"text/x-python; charset=utf-8"},{"id":"45e4c4c0-1835-5f1f-968d-8b45a45372de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/45e4c4c0-1835-5f1f-968d-8b45a45372de/attachment.py","path":"mck_ppt/review.py","size":29301,"sha256":"13392cd31ea2b8753518aa4c94a71ee72fd382695967294bd24e0b4a6a46ffca","contentType":"text/x-python; charset=utf-8"},{"id":"ad539dde-2bdd-5302-9528-5a2332f057fe","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ad539dde-2bdd-5302-9528-5a2332f057fe/attachment.py","path":"mck_ppt/storylines/__init__.py","size":279,"sha256":"405f035b5d415a2020396e3e3891c0a5d45b4e930dd2612bcd4c7831cc1fce19","contentType":"text/x-python; charset=utf-8"},{"id":"87deb883-3697-55dd-a7ad-459963c05284","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/87deb883-3697-55dd-a7ad-459963c05284/attachment.py","path":"mck_ppt/storylines/ai_enterprise.py","size":28807,"sha256":"5881ecffe44eaa2989164e7f3a259fc2cbe87b399904eb03d39a2c36bd4ac130","contentType":"text/x-python; charset=utf-8"},{"id":"0d1ac026-df22-5bb8-8b35-1a7aa4b4c842","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0d1ac026-df22-5bb8-8b35-1a7aa4b4c842/attachment.md","path":"references/INDEX.md","size":3941,"sha256":"73bf929d216630cecd685571d74aa987036d19607da793f1e61f45dc245a5216","contentType":"text/markdown; charset=utf-8"},{"id":"6f2e2003-3616-5131-a98b-ec3a329ae663","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6f2e2003-3616-5131-a98b-ec3a329ae663/attachment.md","path":"references/color-palette.md","size":1120,"sha256":"f122c253a769a6d77459703dd47f3e5bdcfc53359ab3eaa2940dc461ba96e525","contentType":"text/markdown; charset=utf-8"},{"id":"0eddaa3e-4313-59cc-8ab0-cfb85c701afc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/0eddaa3e-4313-59cc-8ab0-cfb85c701afc/attachment.md","path":"references/framework/engine-api.md","size":8901,"sha256":"49c73504e92918a88730499a763b13c92c8eb7479ea571bff47298396071337c","contentType":"text/markdown; charset=utf-8"},{"id":"db69d57a-5189-544c-b838-3c7e0b94be29","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/db69d57a-5189-544c-b838-3c7e0b94be29/attachment.md","path":"references/framework/guard-rails.md","size":4283,"sha256":"f6cff4b6ee00d19fd45d65b03dff4ef9d4595c977d872d2557ba33898515c2b2","contentType":"text/markdown; charset=utf-8"},{"id":"bd74fbd2-139d-5b55-a857-a0d3e6b3637f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bd74fbd2-139d-5b55-a857-a0d3e6b3637f/attachment.md","path":"references/framework/planning-guide.md","size":2174,"sha256":"e6e361433bd64b1bd90c1b68c44afcc41bb1a1e3bd981dc3a0d2e5acd44e75e2","contentType":"text/markdown; charset=utf-8"},{"id":"320e5570-2719-5ae5-a34b-bb15fa688999","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/320e5570-2719-5ae5-a34b-bb15fa688999/attachment.md","path":"references/layout-catalog.md","size":5630,"sha256":"17c01ff23e6e6889387129d926c60eeed597f541a95eca9a7485187ee783d4e5","contentType":"text/markdown; charset=utf-8"},{"id":"2ac83194-4b79-51f4-98fb-3e9aec8ff801","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2ac83194-4b79-51f4-98fb-3e9aec8ff801/attachment.yaml","path":"references/layout-matrix.yaml","size":3843,"sha256":"e154d13f93a91291e069836983eccacf360308893a14e862b52ce17bfad01b37","contentType":"application/yaml; charset=utf-8"},{"id":"5b8bedc7-d51d-5b0d-9df6-e414f1e35790","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5b8bedc7-d51d-5b0d-9df6-e414f1e35790/attachment.py","path":"references/scripts/gate_check.py","size":6313,"sha256":"2896daeaea753ff4ed1aed450856fc0dad58f7e865b866bf1d7aa7d6f94f2174","contentType":"text/x-python; charset=utf-8"},{"id":"19afebcd-33c1-532b-916c-d7fc9f84dd0d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/19afebcd-33c1-532b-916c-d7fc9f84dd0d/attachment.py","path":"references/scripts/gate_check_s3.py","size":14015,"sha256":"5da0e015d5365f1ac2c1cd461eaf465ea909bad2ac20dc97cb77f1329d1e5fe9","contentType":"text/x-python; charset=utf-8"},{"id":"4f8c39e9-645a-5310-9a29-3be3f1361edc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4f8c39e9-645a-5310-9a29-3be3f1361edc/attachment.md","path":"references/team/brand-guide.md","size":2263,"sha256":"08e2bab53ddaaae4246a5e7b3bd0c29b507477652abada8657e04ed1eab8ee35","contentType":"text/markdown; charset=utf-8"},{"id":"3b93d96f-5209-5b4d-81c1-d4516a2ba487","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3b93d96f-5209-5b4d-81c1-d4516a2ba487/attachment.md","path":"references/team/presentation-convention.md","size":2091,"sha256":"75abb9a580581abae4f6efbb83bad3bf3a49bfbfec7aa63e3a3c3fa26fa98135","contentType":"text/markdown; charset=utf-8"},{"id":"d5907ceb-15b1-5266-962d-99aa2b5c1cde","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d5907ceb-15b1-5266-962d-99aa2b5c1cde/attachment.py","path":"run_qa_tests.py","size":31207,"sha256":"0a08ced7fe3aaedffad067c25277b148c896781d17010d50ef89681883a03c2c","contentType":"text/x-python; charset=utf-8"},{"id":"5205fe71-cdf2-55f7-9b48-75bb5f853ec8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5205fe71-cdf2-55f7-9b48-75bb5f853ec8/attachment.py","path":"scripts/generate_icons.py","size":8265,"sha256":"e821c7a3b5ee680795aca76e2fb370e5ae7a72f0562513f843832cc378df4030","contentType":"text/x-python; charset=utf-8"},{"id":"d6bd7f48-f85e-5769-82f7-6dbe7b702ae7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d6bd7f48-f85e-5769-82f7-6dbe7b702ae7/attachment.py","path":"scripts/minimal_example.py","size":8923,"sha256":"019437c1ef47f45560acdbcfed557328b847bb143e003f51091ccdeb55ec8fd2","contentType":"text/x-python; charset=utf-8"},{"id":"a4234e9f-f580-5b62-bd34-3312bcd2225b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a4234e9f-f580-5b62-bd34-3312bcd2225b/attachment.txt","path":"scripts/requirements.txt","size":96,"sha256":"dd4e35499eceb4b7a6a2c72da1215277b37597574b1f48660623a9c534d66e79","contentType":"text/plain; charset=utf-8"}],"bundle_sha256":"ffa73a1405432f3603fd699717275cff6fa9626e27ae93cd97c5dd342fecaafd","attachment_count":51,"text_attachments":39,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":12,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"data-analytics","category_label":"Data"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"data-analytics","import_tag":"clean-skills-v1","description":"Create professional, consultant-grade PowerPoint presentations from scratch using MckEngine (python-pptx wrapper) with McKinsey-style design. Use when user asks to create slides, pitch decks, business presentations, strategy decks, quarterly reviews, board meeting slides, or any professional PPTX. AI calls eng.cover(), eng.donut(), eng.timeline() etc — 67 high-level methods across 12 categories (structure, data, framework, comparison, narrative, timeline, team, charts, images, advanced viz, dashboards, visual storytelling), consistent typography, zero file-corruption issues, BLOCK_ARC native shapes for circular charts (donut, pie, gauge), production-hardened guard rails for spacing, overflow, legend consistency, title style uniformity, dynamic sizing for variable-count layouts, horizontal item overflow protection, chart rendering, and AI-generated cover images via Tencent Hunyuan 2.0 with professional cutout, cool grey-blue tint, and McKinsey-style Bézier ribbon decoration."}},"renderedAt":1782981228993}

McKinsey PPT Design — Harness Framework Version : 2.3.3-harness-v2 · Engine : MckEngine (python-pptx) · Author : likaku Required tools : Read, Write, Bash · Requires : --- 常见崩坏模式(每次生成前必读,先于 HARD RULES) 告诉 AI 要做什么效果一般,告诉它前人最容易在哪里塌效果更好。 以下三种反模式均已在真实执行中被验证,每次必须主动警惕。 反模式 1:口头宣布"门禁通过"(最常见) 错误做法 : 「S4 QA 共 7 个 errors,判断均为 engine 设计行为,门禁通过,进入 S5」 问题所在 : 是由 AI 口头判断的,不是由程序派生的。无论理由多充分,这句话都是 AI 在给自己写完成证书。 正确做法 : 1. 执行 2. 读取 3. 只有 中 时,才能进入 S5 4. 如果 ,修复 列表中的问题,重新渲染,再次执行 gate check --- 反模式 2:S3 门禁"脑子里过一遍"就算通过 错误做法 : 「S3 内容门禁预检:API 格式正确,字数在限制内,通过,进入 S4」(没有执行任何代码) 问题所在 :今天真实发生的 3 个 API 格式错误( / / 参数格式),靠脑子过是过…