はじめに

こんにちは!
KDDIアイレットの取り組みとして、8月17日から8月28日まで開催中の「Google Cloud 夏休み自由研究ブログリレー」、本日は4日目の投稿です。
今回は「Cloud Run Jobs × Gemini で進捗報告の準備を自動化」をテーマに、実際に検証してみた内容をご紹介します!
これまでの記事はこちらです。

記事一覧はこちら

背景

毎週お客さんへの進捗報告があり、これまでは以下の手順を踏んでいました。

  1. 関係する課題を Backlog から人力で探す
  2. 課題ごとに文章を考える
  3. 報告フォーマットに当てはめて整形する

この作業に毎週約30分かかっていました。

また、人力で探す以上、課題の見落とし=報告漏れのリスクも付きまといます。

この非生産的な定型作業を自動化した、という話です。

システム概要

毎週木曜の朝 10:00 に、先週の Backlog 課題を自動で収集・要約して Slack に投稿するシステムを作りました。

全体構成

サービス 使いどころ
Cloud Scheduler 毎週木曜 10:00 に Job を起動。JST で直接指定できる
Cloud Run Jobs 週1回・数分のバッチ処理
Backlog API 先週1週間分の課題・コメントを取得
Cloud Storage 「先週の状態」の保存先。前週との差分で持ち越し課題を検出
Vertex AI(Gemini) 課題の要約・次のアクション整理を担当

週1回・数分で終わる処理なので、常駐サービスではなく Cloud Run Jobs がぴったりでした。

コンテナ化されたスクリプトを用意するだけで、サーバーの面倒を見ずに済みます。

動作の流れ

  1. 先週1週間分の Backlog 課題とコメントを収集
  2. 課題ごとに「今どういう状況か」「次のアクションは何か」を Gemini(Vertex AI)で要約
  3. 担当ボール別(自社 / 協力会社 / クライアント)に整理して Slack に投稿

人間の作業は「確認してコピペするだけ」です。

開発の経緯

最初は Claude Code のスキルとして作りました。

Markdown に手順を書いておくと、「週報作って」と打つだけで半対話的に実行できます。

この段階で実運用しながら、以下を改善しました。

  • ボール判定のルール
  • 要約の粒度・出力フォーマット
  • 持ち越し課題の扱い

ルールが安定した時点で「もう人間が起動する必要なくない?」となり、スクリプト化して Cloud Run Jobs に載せ替えました。

まず対話型で運用してルールを固め、安定したら自動化する、というのが LLM を使った業務自動化のおすすめの進め方です。

工夫したポイント

① Cloud Storage を「前週の記憶」にして持ち越し課題を検出

週報で一番漏れやすいのが「先週報告したけど今週動きがなかった課題」です。

今週の更新分だけ見ていると消えてしまいます。

そこで、毎週のレポートと一緒に収集した課題データの JSON も Cloud Storage に保存しておき、翌週に前週分と突き合わせて、未完了のまま動きがない課題を「持ち越し」として拾います。

def get_carryover_issues(bucket_name, current_json_filename, current_keys):
    """前週 JSON から今週更新がない未完了課題を持ち越す"""
    prev_data = get_prev_weekly_json(bucket_name, current_json_filename)
    if not prev_data:
        return []

    prev_issues = {i["key"]: i for i in prev_data.get("issues", [])}
    missing_keys = set(prev_issues.keys()) - current_keys  # 今週更新なし

    carryover = []
    for key in missing_keys:
        issue = prev_issues[key]
        if not issue.get("is_done"):  # 未完了なら持ち越し
            carryover.append(issue)
    return carryover

DB を立てるほどでもない「前回実行時の状態」の保持に、Cloud Storage はちょうどいい選択肢です。

バケットに JSON を置くだけなので、管理するものが何も増えません。

② 分類はコード、要約だけ LLM に任せる

課題を「どの会社のボールか」で分類する処理は、あえて LLM を使わず担当者名ベースのルールで書いています。

SECTION_RULES = [
    (lambda name: "ClientA" in name, "ClientA"),
    (lambda name: "VendorPrefix" in name, None),          # 除外
    (lambda name: "Vendor" in name, "VendorB"),
    (lambda name: any(n in name for n in ["dev_a", "dev_b", "dev_c"]), "スマホ"),
]

分類を間違えると「他社ボールの課題が自社の遅延として報告される」ような事故になるからです。

決定的に処理できるもの(分類・フィルタリング・フォーマット)はコードで書き、LLM は要約に使う役割分担が安定します。

③ プロンプトで出力の「型」を縛る

要約プロンプトで特に効いたルールは以下です。

- 決定事項・次のアクションは省かない
- 次のアクションがない場合は「〇〇さん確認待ち」など待ち状態を明記する
- 1課題あたり2〜3行を目安にする
- 複数の課題をまとめた注釈(「※〇〇は...」形式)は絶対に使用しない。
  必ず各課題を個別エントリとして出力する

特に最後のルールが重要でした。

LLM は気を利かせて「※上記3件はいずれもレビュー待ち」のようにまとめてくれるのですが、報告としては課題ごとに独立したエントリになっていないと使えないので、明示的に禁止しています。

また、詳細(description)もコメントもない課題(タイトルだけの起票)は LLM に投げても意味がないので、「担当者さん確認・対応待ち」の固定テンプレートにフォールバック(代替処理)させています。

def build_fallback_entry(issue):
    """description/comments がない課題の固定テンプレエントリを生成"""
    url = f"https://your-domain.backlog.jp/view/{issue['key']}"
    assignee = issue.get("assignee", "未設定")
    last_mentioned = issue.get("last_mentioned_user")

    lines = [
        f"• <{url}|{issue['key']}> *{issue['summary']}*",
        f"  ◦ 次のアクション: {assignee}さん確認・対応待ち",
    ]
    if last_mentioned and last_mentioned not in assignee:
        lines.append(f"  ◦ ⚠️ 要ボール確認(担当者: {assignee} / 最終メンション: {last_mentioned})")
    return "\n".join(lines)

担当者と最後にメンションされたユーザーが一致しない場合に ⚠️ フラグを付けて「ボールが渡っているのに担当者が変わっていない」状況を検出しています。

コメント上では別の人に依頼しているのに Backlog の担当者が古いまま、というよくある見落としを拾えます。

効果

項目 Before After
作業時間 約30分/週(課題探し+文章作成+整形) 約2分(確認してコピペするだけ)
報告漏れ 人力での探し漏れあり 防止(機械的に全件拾う)

浮いた時間は、リスク管理や課題の深掘りといった本質的な業務に充てられるようになりました。

また、Slack の出力は Google ドキュメントへコピペし、テキストを選択して箇条書きアイコンをクリックするだけで整形できるため、そのまま進捗報告書として使えます。

まとめ

  • 週1バッチは Cloud Scheduler + Cloud Run Jobs で手軽かつ低コストに構築できる
  • Cloud Storage を「前回実行時の状態」の置き場にすると、持ち越し検出のような差分処理が DB なしで実現できる
  • LLM は要約だけに使い、分類・整形はコードで決定的に処理する
  • いきなり自動化せず、まず対話型 AI で運用してルールを固めてからスケジューラーに載せ替えるのが成功の近道