fix(ppt): 修生成 PPT 缺图形(扁平 deck 质检 gate + 策略层视觉下限)(bump 0.33.4)

延续缺图标排查,统计最近 ppt生成 任务 24 页 SVG 的元素构成:<path>=0、
<image>=0,整本是 <text> 摞 <rect>(文字方块),零示意图/图表/配图。根因同
图标——71 个 charts/ 模板没用、content→版式映射形同虚设,且策略层把"Not every
page needs a chart"当跳过口子(spec_lock 实际 page_layouts: free design、无
page_charts 段),输出层又无 gate 拦扁平 deck。两层修(用户选定):

- A' 输出 gate(svg_quality_checker):统计每页图形图元 <path>/<polyline>/
  <polygon>/<image>(rect/line 是版面脚手架不算);≥6 页且文字密集(avg <text>
  ≥10/页)却全 deck 0 图元 → deck 级 error 退非零(逼回执行重写);多数页无图元
  → INFO;<6 页豁免(不误伤极简/teaser)。实测:8 页文字方块→exit 1;任一页带
  path→放行;4 页→豁免。

- B' 策略层视觉下限(strategist.md GATE):把 §633「Template Match」从纯建议升为
  硬下限——内容 deck(≥6 页)每个能结构化的内容页必须分配视觉处理(page_charts
  模板 / page_layouts 结构模板 / §VII 自绘示意图),spec_lock 不许 page_charts +
  page_layouts 同时空着;给出 content→图形映射速查;明示下游 A' 会硬卡。同步改
  SKILL §大纲映射纪律 + §阶段四质检清单 + spec_lock_reference page_charts 段。

诚实边界:prompt+gate 抬下限(逼别交全文字 deck),执行模型设计功力是上限;gate
守"零图形"底线而非"每页必图表",避免误伤极简风。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
caoqianming 2026-06-30 14:37:28 +08:00
parent 5d23ee682b
commit 4a6182a76a
6 changed files with 82 additions and 4 deletions

View File

@ -2,7 +2,7 @@
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`
最后更新:2026-06-30(ppt skill 修图标管线四层断点:executor 不放图标/质检不拦/工具断/导出不兜底 + bump 0.33.3)
最后更新:2026-06-30(ppt skill 修"缺图形":扁平 deck 质检 gate + strategist 视觉下限强制 + bump 0.33.4)
---
@ -21,6 +21,12 @@
## 已完成关键能力
### 2026-06-30 / ppt skill 修「生成的 PPT 缺图形」:扁平 deck 质检 gate + 策略层视觉下限(bump 0.33.4)
延续缺图标排查,统计最近 ppt生成 任务 24 页 SVG 的元素构成:**`<path>`=0、`<image>`=0**,整本是 `<text>``<rect>`(文字方块),零示意图/图表/配图。根因同图标——71 个 `charts/` 模板没用、content→版式映射形同虚设,且策略层把"Not every page needs a chart"当跳过口子(spec_lock 实际 `page_layouts: free design`、无 page_charts 段),输出层无 gate 拦扁平 deck。两层修(用户选定):
- **A' 输出 gate(svg_quality_checker)**:统计每页图形图元 `<path>/<polyline>/<polygon>/<image>`(`rect`/`line` 是版面脚手架不算);**≥6 页且文字密集(avg `<text>`≥10/页)却全 deck 0 图元 → deck 级 error 退非零**;多数页无图元 → INFO;<6 页豁免(不误伤极简/teaser)。实测:8 页文字方块exit 1;任一页带 path放行;4 豁免
- **B' 策略层视觉下限(strategist.md GATE)**:把 §633「Template Match」从纯建议升为硬下限——内容 deck(≥6 页)每个能结构化的内容页必须分配视觉处理(page_charts 模板 / page_layouts 结构模板 / §VII 自绘示意图),**spec_lock 不许 page_charts + page_layouts 同时空着**;给出 content→图形映射速查;明示下游 A' 会硬卡。同步改 SKILL §大纲映射纪律 + §阶段四质检清单 + spec_lock_reference page_charts 段。
> 诚实边界:prompt+gate 抬下限(逼别交全文字 deck),执行模型设计功力是上限;gate 守"零图形"底线而非"每页必图表",避免误伤极简风。
### 2026-06-30 / ppt skill 修「生成的 PPT 缺图标」四层断点(bump 0.33.3)
查真实用户(caoqianming@foxmail.com)两个「ppt生成」任务的 DB 执行轨迹:24 页 SVG 共 0 个 `<use data-icon>`。根因是图标管线四个环节没有一个强制图标落地——**策略层(有时)锁图标,执行层不放、质检层不拦、工具层还断着**。四层一起修:
- **B 工具断点**:references/SKILL 里 23 处路径仍指向已不存在的 `skills/ppt-master/`(zcbot 是 `skills/ppt/`)→ 模型按文档 `ls .../icons/<lib>/|grep` 验名得空集 → 放弃图标;且 strategist 强制用的 `icon_sync.py` 在 zcbot 根本没有(GATE 空转,正是某任务连图标都没锁的原因)。修:全量改路径 + 新建 `skills/ppt/scripts/icon_sync.py`(复用 embed_icons 解析,验名+拷进 project/icons,缺名非零退出)。

View File

@ -1,3 +1,3 @@
# zcbot 版本号单一事实源:web/app.py 的 FastAPI version、/healthz 返回、前端展示都引这里。
# 改版本只动这一行。
__version__ = "0.33.3"
__version__ = "0.33.4"

View File

@ -96,7 +96,7 @@ description: 生成 PowerPoint 演示文稿 (.pptx) 文件。✅ 触发:用户
**逐页大纲**(写进 design_spec.md §IX,也是 spec_lock 的 page_rhythm/page_layouts 依据):**论断式标题 + 每页标节奏**(`anchor`/`dense`/`breathing`)。三条硬纪律(大纲阶段定死):
- **论断标题**:写结论不写主题("渗透率破 60%" 不是 "行业背景");
- **节奏不雷同**:相邻内容页不同版式;narrative 真正停顿处插 `breathing`(单概念/金句/大图,**禁多卡网格**);不要为凑节奏造填充页;
- **内容→版式映射**:历程→时间轴、循环→闭环、2-4 数字→KPI、并列→网格、单震撼数字→breathing 大字、≥3 数据点→图表(charts/ 模板或自绘)。
- **内容→版式映射(必须落到 spec,不能整本留空)**:历程→时间轴、循环→闭环、2-4 数字→KPI、并列→网格、单震撼数字→breathing 大字、≥3 数据点→图表(charts/ 模板或自绘);对比→象限/分栏、流程→process_flow、占比→donut、架构→分层、关系→hub_spoke。每个能结构化的内容页都要在 spec_lock 的 `page_charts`/`page_layouts` 落一个视觉处理 —— **内容 deck 不许 page_charts + page_layouts 同时空着**(=啥图都没分配,执行层必堆文字方块)。视觉下限见 strategist.md「GATE — visual floor」;质检会硬卡"全是文字方块"的扁平 deck(见阶段四)。
大纲连同 ah **一起给用户预览,⛔ BLOCKING 等确认整份结构**后再进阶段二(改文字比改 slide 便宜)。
@ -137,7 +137,7 @@ references/visual-styles/<locked-style>.md # 锁定的视觉风格
```
.venv/Scripts/python.exe <skill_dir>/scripts/svg_quality_checker.py <project_dir>
```
- **任何 `error`(禁用特性 / viewBox 不符 / spec_lock 漂移 / **锁了图标 inventory 却全 deck 0 图标** 等)必须改:回阶段三重写该页再跑**,不放过。
- **任何 `error`(禁用特性 / viewBox 不符 / spec_lock 漂移 / **锁了图标 inventory 却全 deck 0 图标** / **内容 deck 全是文字方块(≥6 页且零 `<path>`/`<polygon>`/`<polyline>`/`<image>`)** 等)必须改:回阶段三重写该页再跑**,不放过。
- `warning`(低分辨率图 / 非 PPT 安全字体等):能顺手改就改,否则知会后放行。
- 跑 `svg_output/`(不要在 finalize 后跑 —— finalize 改写 SVG 会掩盖源级违规)。

View File

@ -639,6 +639,11 @@ The catalog covers **both data charts and structural information designs**. A "m
The most common Strategist failure mode is missing the structural half — treating "chart" as "numeric chart only" and leaving team / agenda / principles / journey pages as text-only when a template would fit. Read the catalog with both lenses.
> 🚧 **GATE — visual floor (the deck must not ship as text-on-rectangles).** "Not every page needs a chart" is true, but an informative multi-page deck that assigns **zero** visual treatments is the #1 quality failure: flat walls of text in boxes, no diagrams / figures / charts / imagery. Hard floor for any content-bearing deck (≥6 pages, not a pure title/quote teaser):
> - Every **content page whose shape matches a catalog Pick clause** MUST get a visual treatment — a `page_charts` entry (chart / infographic template), a `page_layouts` structural template, or an explicit §VII custom-diagram plan (`no-template-match` with the figure described). Text-in-boxes is the fallback only for pages that genuinely carry no structurable shape, and you must be able to name why.
> - **`spec_lock.md` MUST NOT ship a content deck with `page_charts` empty AND `page_layouts` empty/free-design AND no §VII custom-diagram rows.** That combination means no visual was assigned anywhere — re-scan §IX and map each content page's shape to a figure (comparison→columns/quadrant, process/历程→timeline/process_flow, cycle/循环→concentric/segmented_wheel, share→donut/pie, trend→line/area, ranking→bar, architecture→layered_architecture, relations→hub_spoke/mind_map).
> - Downstream enforcement: `svg_quality_checker.py` **hard-fails** any deck (≥6 text-heavy pages) whose Executor output has zero `<path>`/`<polygon>`/`<polyline>`/`<image>` deck-wide. Leaving the visual plan empty here guarantees that failure later — assign the figures now, at the spec stage, where it is cheapest to change.
> **Reading is mandatory; the catalog is a starting point, not a copy target.**
> - Fully read `templates/charts/charts_index.json` **before drafting the Eight Confirmations** — the read happens up front, not when you sit down to write Section VII. The file contains `meta` + `charts.<key>.summary` only; each `summary` is a selection rule (`"Pick for … Skip if …"`), not a description. There is **no category, quickLookup, or keyword index** — selection is done by semantically matching each page's content shape against all 71 summaries in one pass.
> - Not every page needs a chart. When a page's information structure matches a catalog entry, **use that template as a structural starting point** — keep the visualization type and core layout logic, then adapt composition, density, color, decoration, and accompanying elements to fit this deck's content and visual tone. Free adjustment is encouraged; what is forbidden is (a) generating without reading the catalog, and (b) blind verbatim mimicry that ignores the page's actual content weight.

View File

@ -232,6 +232,17 @@ class SVGQualityChecker:
self._icon_inventory_declared = False # any page's spec_lock locked icons
self._deck_icon_total = 0 # total <use data-icon> across the deck
self._pages_missing_icons: List[str] = [] # declared-but-icon-less pages
# Visual-richness aggregation (non-template mode). The most common AI-deck
# regression is "wall of text boxes": every page is <text> on <rect> with
# zero diagrams, charts, figures, or imagery. <path>/<polyline>/<polygon>/
# <image> are the unambiguous "this page draws something" primitives —
# rect/line are layout/divider scaffolding and don't count. A content-rich
# deck (>=6 pages, text-heavy) with zero such primitives deck-wide is the
# flat-deck pathology; catch it so it can't ship silently.
self._deck_page_count = 0 # non-template SVG pages checked
self._deck_graphic_total = 0 # path+polyline+polygon+image across deck
self._deck_text_total = 0 # <text> across deck (density signal)
self._pages_no_graphic: List[str] = [] # pages with zero graphic primitives
def check_file(self, svg_file: str, expected_format: str = None) -> Dict:
"""
@ -316,6 +327,10 @@ class SVGQualityChecker:
if not self.template_mode:
self._check_icon_usage(content, svg_path, result)
# 12. Check visual richness (flat text-on-rectangles deck).
if not self.template_mode:
self._check_graphic_richness(content, result)
# Determine pass/fail
result['passed'] = len(result['errors']) == 0
@ -1455,6 +1470,53 @@ class SVGQualityChecker:
print(" Cover / section / closing pages may legitimately omit icons; "
"verify dense content pages aren't missing them.")
def _check_graphic_richness(self, content: str, result: Dict) -> None:
"""Tally graphic primitives per page for the deck-level flat-deck gate.
Counts <path>/<polyline>/<polygon>/<image> the elements that actually
draw a diagram, chart, figure, or photo. <rect> and <line> are excluded
on purpose: they are layout cards, backgrounds, and dividers, and a deck
built entirely from them is exactly the "text on rectangles" look this
catches. Per-page nudges stay soft; the hard gate is deck-wide.
"""
g = (len(re.findall(r'<path\b', content))
+ len(re.findall(r'<polyline\b', content))
+ len(re.findall(r'<polygon\b', content))
+ len(re.findall(r'<image\b', content)))
result.setdefault('info', {})['graphic_count'] = g
self._deck_page_count += 1
self._deck_graphic_total += g
self._deck_text_total += len(re.findall(r'<text\b', content))
if g == 0:
self._pages_no_graphic.append(result.get('file', '?'))
def _print_graphic_summary(self):
"""Deck-level flat-deck gate.
A content-rich deck (>=6 pages, text-heavy) with zero path/polygon/
polyline/image deck-wide is the wall-of-text-boxes pathology hard
error so it can't ship. Below that bar, surface a soft note when most
pages carry no figure. Short or sparse decks (<6 pages) are exempt to
avoid false-failing minimalist / teaser decks.
"""
pages = self._deck_page_count
if pages < 6:
return
avg_text = self._deck_text_total / pages
if self._deck_graphic_total == 0 and avg_text >= 10:
self.summary['errors'] += 1
print(f"\n[ERROR] Visual richness: {pages} text-heavy pages but ZERO "
"diagram/figure primitives (<path>/<polygon>/<polyline>/<image>) "
"deck-wide — the deck is text on rectangles.")
print(" Map content shape -> a visual: comparison->columns/quadrant, "
"timeline->process, share->donut, trend->line, ≥3 data points->chart "
"(adapt a templates/charts/ template or draw it), add diagrams/imagery, "
"then re-run.")
elif len(self._pages_no_graphic) >= max(6, int(pages * 0.7)):
print(f"\n[INFO] Visual richness: {len(self._pages_no_graphic)}/{pages} pages "
"have no diagram/figure (<path>/<polygon>/<polyline>/<image>) — "
"verify dense content pages aren't just text + boxes.")
def print_summary(self):
"""Print check summary"""
print("=" * 80)
@ -1486,6 +1548,9 @@ class SVGQualityChecker:
# Deck-level icon-usage gate (declared inventory but icon-less deck).
self._print_icon_summary()
# Deck-level flat-deck gate (text-on-rectangles, no diagrams/figures).
self._print_graphic_summary()
# Fix suggestions
if self.summary['errors'] > 0 or self.summary['warnings'] > 0:
print(f"\n[TIP] Common fixes:")

View File

@ -146,6 +146,8 @@
>
> **Whole section omitted** → no data-visualization pages in this deck.
>
> ⚠️ **Visual floor**: a content-rich deck (≥6 pages) must not leave `page_charts` empty AND `page_layouts` empty/free-design AND carry no §VII custom-diagram rows — that means zero visual treatment was assigned and the Executor will ship a flat text-on-rectangles deck (which `svg_quality_checker.py` hard-fails). See strategist.md "GATE — visual floor".
>
> **Strategist source**: copy from `design_spec.md §VII Visualization Reference List` — only the rows whose `reference template path` points to a `templates/charts/` file. Pages marked `no-template-match` in §VII MUST NOT appear here.
## forbidden