型付き辺: 出 4 / 入 0
ADR-0122: 提案資料生成パイプライン (.md + 会社紹介PPTXテンプレ → ブランド一致.pptx) を Slide-IR(JSON) ベースで構築する
- Status: Accepted
- Mode: Standard
- Kruchten Type: Existence/Property
- Scope: platform
- Implementation Status: Not Started
- 起案者: [email protected]
- 起案日時 (JST): 2026-06-05 04:06
- 承認日時 (JST): 2026-06-05 (PR #1483 merge = 受理・JST 19:29)
- Deciders: [email protected] (単独)
1. コンテキスト
1.1 背景
2026-06-01 に 3 モデル突合調査を実施した結果、会社紹介 PPTX テンプレート (テーマ・スライドマスター・ロゴ・ブランドフォント) と .md 原稿から、ブランド一致かつ編集可能な提案資料 (.pptx) をブレなく生成する仕組みが存在しないことが確定した。現状は手動作成で、(1) 工数が線形に膨らむ、(2) 色・フォント・余白がブランドガイドから逸脱する、(3) 日本語が文字化けする、という 3 つの再発リスクを抱える。
1.2 現状 (As-Is)
定量・技術的事実 (3 モデル突合調査, 2026-06-01):
| 論点 | 確定事実 | 含意 |
|---|---|---|
| 実行環境 | Cloudflare Workers では PPTX 生成が python-pptx / LibreOffice / Pandoc / headless browser いずれも不可 | レンダリングは CI で server-side 実行が必須 |
| テンプレ再利用 | 既存 .pptx 資産を再利用できるのは Pandoc --reference-doc / python-pptx / pptx-automizer (Node) のみ | Marp / Slidev / reveal.js は既定でラスタ画像化・編集不可でブランド再生成に不適 |
| CJK (日本語) | OOXML は <a:latin>/<a:ea>/<a:cs> スロット分離。font.name は latin のみ設定 → 日本語は <a:ea> 未定義で fallback | Han Unification で中国語フォント (SimSun) に落ち「直/込」等が誤字形・豆腐 (▯) 化。最大の落とし穴 |
| LLM の得手不得手 | LLM は HTML/要約は得意、OOXML 直生成は苦手 | LLM に OOXML を直接書かせる設計は破綻する |
1.3 課題
提案資料の品質を人手チェックに依存させ続ける固定費を抱え、資料生成ペースが上がるほど逸脱リスク (色・フォント・余白のブランド逸脱、日本語文字化け) と工数が線形に増える。特に CJK 文字化けは Han Unification で中国語フォントへ無音 fallback するため目視で発見が遅れる。
1.4 制約・要件
- Cloudflare Workers では PPTX 生成不可 → レンダリングは CI server-side 実行必須。
- 既存ブランド .pptx テンプレートを所与とし再利用する (再デザインしない)。
- 日本語
<a:ea>を確実に出力し、Microsoft 365 / LibreOffice Impress で文字化けゼロ。 - LLM に OOXML を直接書かせない (責務分離)。
- decision-pipeline (ADR-0019 / 0066 / 0064 / 0037) のインターフェースを再利用し新規定義しない。
- Cloudflare Workers の 30 秒 wall-time / subrequest 50 件上限を超えない。
1.5 目標 (To-Be)
.md 原稿から Slide-IR(JSON) を中間表現として経由し、HITL 2 ゲート (Markdown 承認 + Slide-IR diff) を挟んで、CI で server-side レンダリングしたブランド一致 .pptx を生成するパイプラインを確立する。Non-Goals: DOM-to-PPTX 本線採用 (freeform_panel エスケープハッチを除く)、Google Slides 経路、テンプレ自体の再デザイン、rendered preview 上での直接編集 (round-trip 不能のため不可)。
2. 決定
明示的な Slide-IR(JSON) を中間表現として採用し、LLM と決定的処理の責務を分離する。Track 2 の 3 モデルが完全一致で支持した方針で、ブランドテンプレ再利用と CJK 安全性を両立できる唯一の構成。LLM は layout 選択・本文要約・title/notes のみを担当し、parse・validate・スロット割当・<a:ea> 付与・OOXML 出力はすべて決定的コードが担う。CI レンダラは python-pptx/Pandoc + pptx-ea-font または pptx-automizer (Node) のいずれかを段階 2 着手前に PoC で確定する。decision-pipeline の LangGraph interrupt() + Queues/DO を再利用し、Workers が同期実行で抱えるのは軽量オーケストレーション (queue 投入・HITL interrupt()・DO state 管理) のみに限定する。LLM 呼び出しと CI レンダリングは Workers 外で実行する。
パイプライン全体像:
Markdown (source of truth)
→ (決定的 segmenter) candidates
→ (LLM: layout 選択 + 本文要約, structured output) Slide-IR(JSON)
→ (決定的 validator) 構造・意味の再検証
→ HITL (Markdown 承認 + Slide-IR diff レビューの 2 ゲート)
→ (CI で python-pptx / pptx-automizer) ブランド一致 .pptx
設計の柱:
- 中間表現 = Slide-IR(JSON)。layout=テンプレ layout 名の enum 参照、content=placeholder 名、text=run 配列 (lang/script 付き)、per-layout 文字数 budget、provenance ブロックを持つ。
- CI server-side レンダリング。(A) python-pptx / Pandoc + pptx-ea-font (成熟・CJK 確実・Python CI)、(B) pptx-automizer (Node、既存ブランド .pptx を改変、TS/Node 親和) の 2 択。
- 責務分離。LLM は layout 選択・本文要約・title/notes のみ。OOXML は決定的コードが担う。
- 決定的 segmentation。H1=section header /
---・slide-level heading=新 slide / 画像・表=単独 slide。layout はテンプレ layout 名の closed enum へ dispatch する (Pandoc named-layout 契約)。 - LLM 制約 3 層。Schema-first (Zod/Pydantic → JSON Schema) → 構造化出力 → 意味的 post-validation。
- CJK 安全性 (無音破損を配布前に決定的検出)。theme に
<a:ea>(Brandwares 手法) + 日本語 run で<a:ea>を必須 emit。pptx-ea-font は python-pptx の内部 XML を直接操作するため python-pptx バージョンアップで静かに無効化され<a:ea>が欠落 → 本番で SimSun フォールバックするリスクがある。これを「想定リスク」でなく配布前に決定的に検出される性質へ落とすため、(a)pip install --require-hashesで python-pptx と pptx-ea-font を同一解決でハッシュロックし Dependabot PR で目視更新、(b) CI レンダリング末尾で生成.pptxを再読込し<a:ea>ノードの存在を XPath でアサート (欠落なら即 fail = 配布前に確実に捕捉)、(c) 直接 XML 操作による<a:ea>付与を pptx-ea-font 非依存のフォールバックとして段階 3 以前に実装する。ブランドフォントは Google Fonts と client PowerPoint の双方に存在する版 (例 Noto Sans JP) に正規化。 - HITL 2 ゲート。Markdown 承認 (必須) + Slide-IR 構造 diff レビュー (推奨)。rendered preview は編集不可 (round-trip 不能)。LangGraph
interrupt()+Command(resume)(approve/edit/reject/respond) と直結。 - オーバーフロー対策。python-pptx の autofit は開くまで未適用のため、決定的 budget + LLM split で対処する (Split > Shrink)。
3. 判断基準 (Decision Drivers)
3.1 評価軸
| # | 軸 | 重要度 (係数) | 案件特有の解釈 |
|---|---|---|---|
| 1 | #reliable | [Must] (×2.0) | 日本語 <a:ea> 文字化けゼロ・ブランド一致 100%。配布前に決定的に検出される設計。 |
| 2 | #maintainable | [Must] (×2.0) | LLM/決定的処理の責務分離、Slide-IR スキーマ単一定義、decision-pipeline 再利用。 |
| 3 | #suitable | [High] (×1.0) | ブランドテンプレ再利用と編集可能 .pptx 出力 (PDF や画像化でなく) を満たす。 |
| 4 | #efficient | [High] (×1.0) | 工数 7 人週・月数百円 LLM 費・CI 無料枠内に収まる。 |
| 5 | #operable | [Medium] (×0.5) | HITL 2 ゲート・CI 完了通知・撤退条件の自動算出が運用可能。 |
K.O. criterion: Must 軸 (#reliable / #maintainable) の score < 3 は不採用。
3.2 評価軸 × 案スコア表
各案 0-5 で評価。加重和 = (Σ score × 係数) / (満点 × Σ 係数) で正規化。
| 軸 | 係数 | 採択案 (Slide-IR 中間表現) | 案 A (LLM が OOXML 直生成) | 案 B (Google Slides + PDF 消費) | 案 C (Marp/Slidev で HTML→PPTX) |
|---|---|---|---|---|---|
| #reliable [Must] | ×2.0 | 5 | 1 | 3 | 2 |
| #maintainable [Must] | ×2.0 | 5 | 1 | 4 | 3 |
| #suitable [High] | ×1.0 | 5 | 2 | 2 | 2 |
| #efficient [High] | ×1.0 | 3 | 4 | 5 | 4 |
| #operable [Medium] | ×0.5 | 4 | 2 | 4 | 3 |
| 加重和 (正規化) | 0.918 | 0.345 | 0.700 | 0.527 | |
| K.O. 通過 (Must ≥3) | ✓ | ❌ | ✓ | ❌ |
4. 検討した代替案 (Alternatives Considered)
- 案 A (LLM が OOXML 直生成): LLM に直接 .pptx の XML を書かせる — LLM は OOXML 生成が苦手で構造破綻・CJK 欠落が頻発し reliable/maintainable とも K.O. 不通過。不採用。
- 案 B (Google Slides 経路 + PDF 消費前提): Google Slides テンプレで生成し PDF で配布 — 編集可能 .pptx 提供という #suitable 要件を満たさず、撤退条件成立時の縮退オプションとして温存。
- 案 C (Marp / Slidev / reveal.js で HTML→PPTX): HTML ベースのスライドツールから PPTX エクスポート — 既定でラスタ画像化・編集不可でブランド再生成に不適、
<a:ea>制御不能で Must 不通過。不採用。
5. 影響 (Consequences)
5.1 正の影響 (Good)
- LLM/決定的処理の責務分離により Slide-IR スキーマ単一の真実源で品質が決定的に保証され、CJK 文字化けが配布前に CI で必ず検出される (XPath アサート + ハッシュロック + 直接 XML フォールバック)。
- decision-pipeline (ADR-0019 / 0066 / 0064 / 0037) の LangGraph・Queues・DO・staging を再利用するため新規構築が中間表現スキーマ・segmenter・レンダラに限定され、追加インフラ課金 0 円。
- ブランドテンプレを所与のまま再利用でき、編集可能 .pptx として配布可能。
- LLM コスト月数百円〜1,000 円、CI は GitHub Actions 無料枠 (2,000 分/月) 内。
5.2 負の影響 (Bad)
- 実工数は楽観値 7 人週に対し、renderer 選定遅延・pptx-ea-font バージョン依存対応・CJK 検証環境差異が重なると1.5〜2 倍 (約 10〜14 人週) に膨張しうる。
- 盲点 #1 (critical): テンプレ layout 名が PowerPoint UI で無警告に変更されると Slide-IR の closed enum が全件リジェクトされ .pptx ゼロ枚生成になる → 段階 2 完了条件に「テンプレ読み込み時に layout 名一覧を自動抽出して Slide-IR スキーマの enum と突合し、不一致が 1 件でもあればレンダリングを開始しない CI ゲート」を必須実装する。
- 盲点 #2 (high): ADR-0066 で設定済みの Queues visibility timeout が CI レンダリング所要 (数分) より短いと at-least-once 配信で二重生成発生 → 段階 2 着手前に timeout 実値を確認し、CI レンダリング最大所要 (10 分 + マージン) を超える値に変更可能か検証。変更不可なら Slide-IR の
job_idを冪等キーとした重複チェックロジックを段階 2 完了条件に明示。 - 盲点 #3 (high): GAS モノレポに Python CI を同居させると既存 GAS/clasp テストと依存解決が干渉 → 「GAS 用と Python レンダラ用のワークフローファイルを別 .yml に分離し runner/environment を完全独立」させ、段階 1 完了前に既存 GAS テストへの影響ゼロを PoC で確認。
- 盲点 #4 (high): Pandoc 経路の named layout dispatch は内部ヒューリスティクス依存で Slide-IR layout enum と非等価 → 段階 1 PoC で 1 対 1 対応を実証し、不可なら Pandoc を補助用途に限定し python-pptx を唯一の layout dispatch 実装と明記。
- 盲点 #5 (high): 提案ラッシュ・再試行ループで GitHub Actions 無料枠 2,000 分/月を使い切ると HITL 承認済みジョブが無音で待機 → 月間消費が 1,500 分超で Slack 通知するスクリプトと、CI ジョブが一定時間内に開始されない場合の HITL 通知監視を段階 2 完了前に実装。
- 盲点 #6 (high): linked 形式のロゴ・OLE オブジェクトは pptx-automizer / python-pptx 双方が実体コピーせず受信者環境でロゴ消失 → 段階 1 PoC でテンプレのメディア参照方式 (embedded / linked) を確認し、linked が存在すれば embed 変換手順または CI media integrity チェックを段階 2 完了条件に追加。
- 盲点 #7 (medium): Google Slides / Keynote インポート時にフォント置換で字形・行間ずれ → パイロット 20 スライド検証条件に「Google Slides インポートおよび Keynote 変換での目視確認」を追加し、再現時の PDF 併送ポリシーを撤退条件と同セクションに記載。
- 盲点 #8 (medium): per-layout 文字数 budget ハードコードはテンプレ改訂で陳腐化 → テンプレ .pptx から placeholder の textbox 高さ・フォントサイズを自動抽出して budget を導出するスクリプトを CI に組み込み、テンプレ更新 PR で budget が自動再計算されることを段階 2 完了条件に追加。
- 盲点 #9 (medium): Slide-IR スキーマフィールドが要件追加のたびに増殖 → 段階 1 完了時点でフィールド数と定義理由を ADR 付録に一覧化し、段階 2・3 着手前に「前段階からの追加フィールド数と追加理由」を記録するルール追加。追加が 3 件超で撤退条件月次レビューを前倒し。
5.3 中立・トレードオフ (Neutral / Trade-offs)
- 実装で確定する Open Question 2 点 (renderer 確定時に決定):
- CJK 粒度: A=run 毎に
script:"Jpan"明示 (正確・冗長) / B=render 時 Unicode 検出 (単純)。推奨ハイブリッド = 既定 B + 混在・font 上書き run のみ A。日本語単一原稿なら B 寄りで開始。 - テンプレ参照方式: pptx-automizer は shape 名キー / python-pptx は idx。renderer 確定に合わせる (いずれも座標でなく名前で参照する点は共通)。
- CJK 粒度: A=run 毎に
- 段階化 (各段で動作確認):
- Slide-IR スキーマ確定 + 決定的 segmenter + Markdown 承認ゲート (低リスク・スキーマと parse のみ)。
- CI レンダラ最小実装 (テンプレ 1 layout で .pptx を 1 枚生成し目視確認)。段階 2 着手前に renderer 確定 (PoC: 既存テンプレ全 layout 読込)。
- CJK
<a:ea>+ Slide-IR diff ゲート + オーバーフロー split を追加し、複数 layout・日本語混在で検証。
- 影響範囲: bizlp-gas-accounting モノレポ、
drp/に相乗り。新規モジュール = Slide-IR スキーマ (Zod/Pydantic) / 決定的 segmenter / LLM layout 選択ノード / 決定的 validator / CI レンダラ / テンプレ整合性チェッカー。再利用 = LangGraph graph / Queues/consumer / DO state。 - 過去 ADR との関係: ADR-0019 (LangGraph) / ADR-0066 (async Queues + DO, Paid plan $5/月相乗り) / ADR-0064 (web UI → CI トリガ) / ADR-0037 (serverside draft staging) に一方向依存。supersede・無効化しない。decision-pipeline 側 ADR のインターフェース変更時は本 ADR 再評価。
- Jr 引き継ぎ容易性 (テナント層審査の条件・Trade-off): LangGraph/Queues/DO/OOXML 直接操作の多層スタックは 2026-10 入社予定の Jr には引き継ぎ負荷が高い。緩和 = 習得対象スタックの最小化を renderer 確定 PoC の判断基準に含める (python-pptx 経路一本化なら Python + OOXML のみで CI 部分が完結し、pptx-automizer 不採用で Node 側学習を回避できる)。案 B (Google Slides + PDF) の初期採用は #suitable 要件 (編集可能 .pptx) を満たさず本線にしないが、1 人法人運用コストで ROI が逆転した場合の縮退先として §6 に維持する。
6. 撤退条件 (Rollback Plan)
以下の客観基準のいずれか 1 つを満たしたら、案 B (Google Slides 経路・PDF 消費前提) へ縮退する。判定はサンクコストバイアスを避けるため月次レビューで自動算出データに基づいて行い、判断記録を ADR 更新履歴に残す。
- パイロット 20 スライド (全 layout × 混在 run × Microsoft 365 / LibreOffice Impress 開封確認 + Google Slides インポート + Keynote 変換目視確認を含む) の検証で、日本語
<a:ea>文字化け (豆腐・誤字形) が使用する renderer いずれかで 1 件以上再現する。 - HITL の Markdown 承認後の人手手直し率 (= PowerPoint 上で人手編集されたスライド枚数 / 総スライド枚数、CI が
.pptxメタデータから自動収集) が 50% を超える案件が 3 案件連続する。 - 段階 1 (Slide-IR スキーマ + 決定的 segmenter + Markdown 承認ゲート) が着手から 4 週を超えても、1 layout の
.pptx生成・目視確認に至らない。 - Slide-IR スキーマフィールド追加が前段階比 3 件を超えた場合、撤退条件月次レビューを前倒し実施 (盲点 #9 対応)。
- Google Slides / Keynote インポートで再現する字形・行間ずれが提案資料として提出不可と判断された場合、PDF 併送ポリシーへの縮退を含め判断する (盲点 #7 対応)。
- Jr 引き継ぎ容易性チェック (テナント層審査の条件): 段階 1 完了時に「2026-10 入社予定の Jr が段階 1 コード (Slide-IR スキーマ + segmenter) を 2 週間でレビュー可能か」を判定基準として評価し、不可と判断したら習得スタックの縮小 (renderer 一本化・pptx-automizer 不採用等) を段階 2 着手の前提条件とする。
コスト試算
(パイプライン生成時に欠落した §コスト試算 H2 を起案者生テキストから復元・通過後追補。個人名は表記規約に従い職位へ置換)
実装工数 (工程別内訳、合計 約 7 人週 = 約 35 人日):
- Slide-IR スキーマ設計 + 既存テンプレ全 layout 棚卸し (closed enum 定義): 1 人週
- 決定的 segmenter (
.md→ candidates): 1 人週 - LLM layout 選択ノード + 決定的 validator + 意味的 post-validation: 1.5 人週
- CI レンダラ (python-pptx または pptx-automizer +
<a:ea>付与 + テンプレ整合性チェック): 1.5 人週 - HITL 2 ゲート統合 (LangGraph interrupt 連携) + 統合テスト: 1.5 人週
- オーバーフロー split + 予備バッファ: 0.5 人週
⚠️ この 7 人週は楽観値。renderer 選定 (python-pptx ↔ pptx-automizer) を段階 2 着手前まで先送りする設計のため確定後に segmenter/validator を一部書き直すリスク、pptx-ea-font の python-pptx バージョン依存 (直接 XML 操作フォールバックの追加実装)、CJK 検証の環境差異対応が重なると 実工数は 1.5〜2 倍 (約 10〜14 人週) に膨張しうる (§5.2 と整合)。リスク低減のため renderer は段階 1 開始前に PoC (既存テンプレ全 layout 読込) で確定させる。
人件費換算: 個人開発 (代表取締役 単独) のため外部発注費 0 円。機会費用として 7 人週 ≒ 35 人日分の自開発時間。
ランニング (月額概算):
- LLM: 1 資料あたり要約 + layout 選択のみ (OOXML は決定的) で入出力 約 2〜5 万トークン → 数十円/本。月 10〜20 本想定で月 数百円〜1,000 円規模
- CI レンダリング: 1 資料あたり 10 分以内 × 月 10〜20 本 ≒ 月 100〜200 分。GitHub Actions 無料枠 (2,000 分/月) 内で追加課金 0 円見込み
- インフラ: decision-pipeline の Cloudflare Worker (ADR-0066 で Paid plan $5/月 を許容済) に相乗りで追加インフラ課金 0 円
- 追加 SaaS: 0 円 (テンプレは既存 .pptx 再利用、フォントは Noto Sans JP 等の無料版)
現状ベースラインの記録 (審査講評対応・Review After の比較基準): 段階 1 開始前に「月間提案資料本数・1 本あたり手動作成工数・ブランド逸脱/文字化けの発生実績」を実測し本節へ追記する。未計測のまま段階 2 へ進まない。運用オーバーヘッドの実測 (テナント層審査の条件): 段階 3 完了時に月次/四半期レビュー・盲点対応・Dependabot 目視の年間総工数を実測集計し、提案資料作成の削減工数と比較した表を Review After の必須入力とする。
7. Confirmation
fitness function:
- 検証手段:
- パイロット 20 スライド (日本語含む・全 layout 網羅・混在 run 含む・Microsoft 365 / LibreOffice / Google Slides / Keynote 開封確認) で
<a:ea>文字化け 0 件 — CI が生成 .pptx を再読込し XPath アサート (盲点 #1 対応で layout 名 enum 突合ゲートを CI 先頭に置く)。 - ブランド色・フォント・ロゴのテンプレ一致率 100% — 生成 .pptx とテンプレの自動 diff (linked メディア integrity チェック含む、盲点 #6 対応)。
- HITL の Markdown 承認後の人手手直し率中央値 30% 以下 — CI が .pptx メタデータから自動収集。
.md原稿 1 本から.pptx生成完了までの CI 所要 10 分以内 (HITL 待ち時間除く) — GitHub Actions の job duration から自動算出、月間消費 1,500 分超で Slack 通知 (盲点 #5 対応)。
- パイロット 20 スライド (日本語含む・全 layout 網羅・混在 run 含む・Microsoft 365 / LibreOffice / Google Slides / Keynote 開封確認) で
- 実行頻度: CI ジョブごと (XPath / layout enum / メディア integrity / 所要時間) + 月次運用レビュー (手直し率・スキーマフィールド増加・無料枠消費)。
- 違反時対応: XPath / layout enum / メディア integrity アサート失敗は即 fail (配布前ブロック)。手直し率 50% 超が 3 案件連続 / 文字化け再現 / 段階 1 が 4 週超で §6 撤退条件発動。スキーマフィールド +3 件超で月次レビュー前倒し。
- Review After: 段階 3 完了から 3 ヶ月後 (本 ADR Accept を 2026-06 とした場合 2026-09〜2026-10) に運用レビュー、以後四半期ごと。負債化トリガ: (a) pptx-ea-font が 6 ヶ月以上メンテ停止または python-pptx 互換破壊で生成が壊れた場合 → 直接 XML 操作フォールバックへ移行判断、(b) ブランドテンプレ改訂時にスキーマ未同期で旧ブランド .pptx が 1 件でも生成された場合 → 同期フロー見直し、(c) Slide-IR スキーマフィールド数が当初設計比 +20% 超で「設計のための設計」退行を点検。
8. 参照 (References)
- 関連 ADR: ADR-0019 (LangGraph 移行) / ADR-0066 (async Queues + DO アーキテクチャ, Paid plan $5/月) / ADR-0064 (web UI → CI トリガ拡張) / ADR-0037 (serverside draft staging ライフサイクル)
- 関連 PR/Issue: -
- 外部資料: Brandwares
<a:ea>theme 手法、Pandoc--reference-doc仕様、python-pptx / pptx-ea-font / pptx-automizer ドキュメント、Cloudflare Queues at-least-once 配信仕様、3 モデル突合調査 (2026-06-01)