0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIにスライドを丸ごと作らせない —— Claude Code × Crashlytics 週次レポート自動生成の設計

0
Posted at

はじめに

iOS / Android の Crashlytics を見て、定例会議向けの週次レポート(PowerPoint)を毎週作る——という定型作業を、Claude Code / Claude Cowork で自動化しました。

この手の「AI でレポート自動生成」は記事も多いのですが、本記事で伝えたいのはツールの使い方ではなく 役割分担の設計 です。結論を先に書くと、こうです。

AI にスライドを丸ごと作らせない。レイアウトはテンプレート(コード)に固定し、AI はデータ取得と JSON 作成に専念させる。

サニタイズしたサンプル一式を GitHub で公開しています。架空の電子書籍アプリ「BookShelf」を題材にしているので、そのまま動かせます。

1. なぜ「AI に丸ごと作らせる」とうまくいかないのか

最初は素直に「Crashlytics MCP でデータを取って、いい感じのスライドを作って」と毎回 LLM に任せていました。これは動くのですが、運用すると問題が出ます。

  • 回によって表がはみ出す・列がずれる
  • 配色やフォントが毎回微妙に違う
  • 「先週と同じ見た目」を維持できない

原因はシンプルで、毎回ゼロからレイアウトを生成しているから です。レイアウトは本来「毎週変わらないもの」なのに、変わる前提で生成すると、揺れがそのまま品質のブレになります。

2. 責務を2つに分ける

そこで、レポート生成を 2 つの責務に分けました。

担当 役割 変わるもの
プロンプト(AI) データ取得・検証・JSON 作成 数字・文章(毎週変わる)
テンプレート(Python) レイアウト・配色・グラフ描画 構造(固定。崩れない)

AI の出力は JSON だけ にします。スライドの見た目は python-pptx で書いた1本のテンプレートスクリプトが、その JSON を受け取って描画します。

python build_report.py --data report_data.json --out weekly_report.pptx

この分け方の効果は、運用してみると効いてきます。

  • 修正範囲が分離する: 「見た目を直したい」ならスクリプトだけ、「取得データを変えたい」ならプロンプトだけ。お互いに影響しない
  • 崩れが構造的に起きない: レイアウトはコードで固定なので、毎回同じ
  • iOS / Android で共通: テンプレートは1本。プラットフォーム差は JSON のキー(アプリ名・フッター等)で吸収する

3つ目は地味ですが重要で、テンプレートが1本だと 改善が両 OS に同時反映 され、フォーマットの差分やメンテ漏れが起きません。

3. プロンプトは「小さな組織の設計図」として書く

プロンプト側(AI の担当)は、ただ「データを取って」ではなく、責務を明文化した指示書にしています。役割を分けて書くと、AI の判断が安定します。

  • 何を取得するか: topIssues(今週・前週・前々週の3期間)、OS 別、サンプルイベント
  • どう検証するか: 取得値が不完全なら推定で補完せず「データなし」にする
  • どう JSON にまとめるか: スキーマと、各フィールドの意味・制約

特に効いたのが データ検証ルールの明文化 です。たとえば「バージョン別集計が FATAL 実数に対して極端に少ないときは、サンプルから推定で埋めず、理由を添えて『データなし』にする」と書いておくと、AI が勝手に数字を作りません。レポートの信頼性は、こうした「やらないことの定義」で決まります。

4. テンプレートに「判断」を持たせる

テンプレート側は単なる描画だけでなく、レポートの一貫性を保つロジックも引き受けます。ここが、ただの pptx 出力スクリプトとの違いです。

4-1. 傾向の矢印は符号から自動決定する

前週比の矢印(↑ ↓ →)を JSON で受け取るのではなく、数値の符号からテンプレートが決定 します。

def trend_text_and_color(pct):
    if pct is None:
        return "", GRAY
    if pct > 5:
        arrow, color = "", RED      # 増加=悪化
    elif pct < -5:
        arrow, color = "", TEAL     # 減少=改善
    else:
        arrow, color = "", GRAY     # ±5%未満は横ばい
    pct_txt = "±0%" if pct == 0 else f"{pct:+d}%"
    return f"{arrow} {pct_txt}", color

こうすると「矢印は↑なのに数値はマイナス」のような不一致が原理的に起きません。判断基準(±5%)も1か所に集約されます。

4-2. 取得できないものは「データなし」と明示する

null を受け取ったら、推定で埋めずに「データなし」「—」と表示します。グラフでも、前週ランク外で実数が取れない週は高さ0+「—」にして、0件と「計測外」を区別 します。0 と表示すると「先週は0件だった」と誤読されるためです。

4-3. 新規 Issue を自動でマークする

今週初めて上位に入った Issue は、グラフに new!! バッジ、テーブルに「新規」と自動表示します。直近3週を並べているので、初出が「前週」か「今週」かもバッジの位置で分かります。

3週推移グラフ(新規バッジ付き)

4-4. 行の高さを内容に合わせる

技術詳細テーブルの行高さは、セルの行数を見積もって決めます。最初は「全文字を全角幅」で計算していたら、クラス名やスタックトレースのような半角英数主体のセルで行数を倍近く過大見積もりし、行が間延びしました。

そこで 実際のワードラップを再現 する方式にしました。半角英数の連続(クラス名・識別子)は折り返し不可の1語として扱い、全角は1文字ずつ折り返す、という貪欲法です。

def estimate_lines(text, width_in, font_pt):
    cap = max(2, int(width_in / (font_pt * 0.0075)))  # 1行に入る半角換算文字数
    total = 0
    for seg in str(text).split("\n"):
        tokens = re.findall(r"[!-~]+|.", seg)  # 英数の連続 or 全角1文字
        lines, line = 1, 0
        for tk in tokens:
            w = sum(1 if ord(c) < 0x80 else 2 for c in tk)
            if line + w <= cap:
                line += w
            elif w > cap:                 # 行幅を超える長語は強制分割
                rest = w - (cap - line)
                lines += math.ceil(rest / cap)
                line = rest % cap or cap
            else:
                lines += 1
                line = w
        total += lines
    return total

地味ですが、SecureKeyStore_GenerateKey のような長い識別子を含むセルの行高さが、文字数ベースの概算では合いません。実物で初めて分かる類の問題です。

5. 「視覚的な差異は情報の差異に対応させる」

レイアウトを固定すると、今度は「色やマークに意味を持たせる」ことに集中できます。私が一貫して守っているのは、

視覚的な差異は、情報の差異に対応させる。意味のない装飾は欠陥である。

という原則です。今回の調整は、ほぼすべてこの原則の適用例でした。

  • KPI カードの強調色は「最重要だから」赤、ではなく 増加(悪化)だから赤・減少(改善)だからティール と意味に固定
  • グラフの色は「今週=クリムゾン」で全シート統一。同じ色=同じ意味(今週の FATAL 件数)
  • 3週推移は時間順に淡グレー→スレート→クリムゾンと濃くして、左から右への時間の流れを直感的に
  • new!! バッジや「新規」表示は、新規という情報を持つものにだけ付く

色を足したくなったら「これは情報の差異を表しているか?」と自問する。表していないなら足さない。これだけで、見た目の説得力が変わります。

6. 実データでしか見つからない問題を、計測で潰す

サンプルデータでは出ず、本番データで初めて顕在化する問題がありました。代表的なものが「実行タイミングのブレ」です。

対象期間を「実行日の前日基準」で決めていたため、定期実行が月曜か水曜かで7日間の窓ごとズレ、前週比や新規判定がブレていました。これは対象期間を 固定の ISO 週(月〜日) に紐づけて解決。実行が何曜でも、対象は常に「直近の完了した1週間」になります。

ポイントは、想像ではなく 実測して原因を突き止めてから直す ことでした。スライドを PDF→画像に変換してピクセル値や行の高さを確認する、という地道な検証が効きました。

soffice --headless --convert-to pdf weekly_report.pptx
pdftoppm -jpeg -r 100 weekly_report.pdf slide

7. 運用:定期実行+ワンクリック再生成

定期実行は Claude Cowork のスケジュール済みタスクとして登録しています。加えて、定例前に最新データで作り直したいときは、タスク画面の再生ボタン(今すぐ実行)を押すだけで任意のタイミングでも生成できます。「自動で毎週」と「必要なときに手動で」の両方が、同じ仕組みで賄えます。

まとめ

  • AI にスライドを丸ごと作らせない。レイアウトはテンプレートに固定し、AI はデータ取得と JSON 作成に専念させる
  • プロンプトは「小さな組織の設計図」。何を取り、どう検証し、どう JSON にまとめるかを責務として明文化する
  • テンプレートには描画だけでなく 判断(矢印・データなし・新規判定・行高さ)を持たせる
  • 色やマークは装飾ではなく 情報の差異 に対応させる
  • 本番データでしか出ない問題は、想像ではなく 計測 で潰す

サンプル一式は GitHub に置いてあります。report_data.sample.json を差し替えれば同じレイアウトで中身だけ変わるので、自分のプロダクト向けに作り替える出発点に使ってください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?