🚀

その分析、Cursorにやらせてみませんか?

に公開

はじめに

最近、Slack に「前日のデータサマリ」を貼るのが地味に面倒になってきました。
クエリ実行 → CSV 出力 → グラフ化 → Slack 投稿。
誰でもできるけど、誰もやりたくない定例作業ですよね。

そこで今回は、Cursor × Snowflake × Slack を使い、自動で分析レポートをSlackへ投稿する仕組みを作ってみました。


cursorの自動レポートによるアウトプット

1. 全体構成

流れ

  1. SnowsqlとCursor CLIをセットアップし、CursorからSnowflakeを分析するための準備を行う。
  2. SQL クエリを作成し、Snowflake からデータを取得
  3. YAML でレポート定義を作成し、AIにMarkdownレポートを生成させる
  4. GitHub Actions で定期実行し、Slack Webhook に投稿

補足:SnowSQL について

SnowSQL は現在も利用可能な CLI ですが、Snowflake 公式では新しい Snowflake CLI の利用が推奨されています。
古いバージョンの SnowSQL はサポート対象外になる場合があり、今後の機能追加も Snowflake CLI が中心となるため、長期的な運用では移行を検討することをおすすめします。

https://docs.snowflake.com/ja/user-guide/snowsql

なお、CursorによるSnowflakeの分析方法については、Snowflake社の菅野様のSnowflake mcpサーバーを用いた方法もあるため、是非ともご参考くださいませ。

https://zenn.dev/tsubasa_tech/articles/70ab5fb2b5ed99

2. ディレクトリ構成

slack-reports/
  ├── .github/                         # 定期実行
  │   └── slack_reports.yaml           # Snowflakeクエリ実行クライアント
  │
  ├── core/                            # メインロジック
  │   ├── analysis_runner.py           # 分析実行のエントリーポイント(YAML→SQL→Slack)
  │   ├── cursor_client.py             # Cursor APIラッパー(AI補完・自動生成管理)
  │   ├── slack_client.py              # Slack Webhook送信モジュール
  │   └── snowflake_client.py          # Snowflakeクエリ実行クライアント
  │
  ├── queries/                         # クエリとレポート定義ファイル
  │   ├── reports.sql                  # 定常分析に使うSQLクエリ
  │   └── reports.yaml                 # SQL実行や出力設定のYAML定義
  │
  ├── tmp/                             # 一時ファイルや生成レポートの格納先
  │   └── reports.md                   # Slack送信用に整形したMarkdownレポート
  │
  └── .env               # webhook URL / Snowflakeのアカウントを環境変数化

3. Cursor CLIのセットアップとスクリプト解説

今回のレポート生成では、Cursor CLIcursor-agent)をPythonから呼び出し、
YAML仕様とCSVデータを渡してAIにMarkdownレポートを出力させています。

Cursor CLIの準備

公式からインストールします:

brew install cursor-ai/tap/cursor-agent   # macOS
# または
npm install -g cursor-agent               # Windows / Linux
# ログインして認証
cursor-agent login

スクリプト側の実装(python)

import subprocess, tempfile
import shutil, textwrap
import os


class CursorClient:
    """Cursor CLIラッパー(プロンプト構築機能を統合)"""

    SENTINEL = "__DATA_UNAVAILABLE__"

    def __init__(self, bin_path=None):
        self.bin_path = bin_path or shutil.which("cursor-agent")
        if not self.bin_path:
            raise RuntimeError("cursor-agent 実行ファイルが見つかりません。")

    def build_prompt(self, yaml_text: str, csv_text: str, sql_name: str) -> str:
        """Cursorに渡すプロンプトを構築"""
        return textwrap.dedent(f"""
        You are an analytics agent running in Cursor CLI.

        INPUT DATA:
        Below is the CSV output of the SQL "{sql_name}".
        If it is empty or unparseable, output exactly:
        {self.SENTINEL}

        CSV:
        ```csv
        {csv_text.strip()}
        ```

        YAML SPEC:
        ```yaml
        {yaml_text.strip()}
        ```

        OUTPUT POLICY:
        - Output ONLY the final Markdown report.
        - No reasoning, logs, or fabricated values.
        """).strip()

    def run(self, prompt: str) -> str:

        with tempfile.NamedTemporaryFile(prefix="cursor_stderr_", delete=False) as errf:
            err_path = errf.name

        proc = subprocess.run(
            [self.bin_path, "--print", "--output-format", "text"],
            input=prompt.encode("utf-8"),
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )

        if proc.returncode != 0:
            raise RuntimeError(
                f"Cursor実行エラー(exit={proc.returncode})\n"
                f"{proc.stderr.decode('utf-8', 'ignore')[:300]}"
            )

        out = proc.stdout.decode("utf-8", "ignore").strip()
        # 出力が空の場合はフェイルセーフのセンチネルを返す
        return out if out else self.SENTINEL

🧩 CursorClient の役割

  • build_prompt:CSV と YAML を受け取り、Cursor CLI が理解できる統合プロンプトを組み立てる
  • run:生成したプロンプトを cursor-agent に渡して実行し、結果の Markdown 出力だけを返す
  • フェイルセーフ:出力が空または異常終了時には DATA_UNAVAILABLE を返す
    テキスト指示 → AI → Markdown という流れが抽象化されているため、
    分析者は SQL と YAML を書くだけで済みます。

4. 分析フォーマット(reports.yaml)

YAML では、「何をどの形式で出すか」「どんなインサイトを出すか」を完全に分離して管理しています。これにより、SQLを変更せずに レイアウトだけ差し替える などの運用が可能になります。

title: '定期便 週次レポート(Slack自動投稿)'
slack_channel: '#project-定期便'

formatting:
  number:
    int: 'comma'
    float: 'comma_1'
  percent:
    default: '1pct'
  emoji:
    up: '📈'
    down: '📉'
    flat: '→'

structure:
  - id: header
    type: text
    template: |
      💌 *定期便 週次レポート*({{ 対象月 }} 時点)
      経過日数:{{ 経過日数 }} / {{ 月総日数 }}

  - id: summary
    type: text
    template: |
      *📦 全体サマリ*
      CV:{{ 当月配CV | intcomma }}件(前月同日比 {{ 前月比進捗率(%) }}%)
      ARPU:¥{{ 当月ARPU | floatcomma }}
      構成比:{{ 当月構成比(%) }}%
      着地予測(前月比):{{ 着地予測_配送数_前月比 | intcomma }}件 / ¥{{ 着地予測_ARPU_前月比 | floatcomma }}
      着地予測(日割):{{ 着地予測_配送数_日割 | intcomma }}件 / ¥{{ 着地予測_ARPU_日割 | floatcomma }}

  - id: segments
    type: table
    header:
      [
        'セグメント',
        '当月配送数',
        '構成比(%)',
        '前月比進捗率(%)',
        'ARPU',
        '着地(日割)',
      ]
    rows_from_sql: true
    sql_mapping:
      セグメント: セグメント
      当月配送数: 当月配送数
      当月構成比(%): 当月構成比(%)
      前月比進捗率(%): 前月比進捗率(%)
      当月ARPU: 当月ARPU
      着地予測_配送数_日割: 着地予測_配送数_日割

  - id: insights
    type: bullet_points
    title: '🧠 インサイト'
    items:
      - '全体配送数は前月同日比 {{ 前月比進捗率(%) }}%。ARPUは ¥{{ 当月ARPU | floatcomma }}。'
      - '日割着地見込み:{{ 着地予測_配送数_日割 | intcomma }}件(¥{{ 着地予測_ARPU_日割 | floatcomma }})。'
      - 'セグメント別では1点構成比低下、2点構成比上昇傾向📈。'

  - id: footer
    type: text
    template: |
      _出力元: Snowflake (box_subscription_reports.sql)_
      _自動投稿: Slack Webhook via auto_report_bot_

ポイント:フォーマットは分析内容に応じて自由に作れる

この YAML 形式はあくまで一例で、必要な出力や分析要件に応じて、自由に拡張/削減して構いません。

  • KPI が多いプロジェクト → テーブルを増やす
  • Weekly レポート → グラフ画像を生成して添付
  • アラート用途 → 「しきい値を下回ったら赤字で表示」

など、レポート構造を YAML で宣言的に管理することで、業務要件に合わせてレポートを高速に組み替えられるのが最大の利点です。

5. 実際の活用事例

シロクでは、各事業部の KPI の進捗をレポーティングし、伸び代やアラートを Slack から即座に検知できる仕組みとして運用しています。
日々の売上指標、顧客セグメントごとの変化など、決裁や意思決定に直結する指標を毎日自動で共有できるため、現場側の状況把握が早まり、必要なアクションを即時に取れるようになりました。

6. まとめ・展望

CursorとSnowflakeを組み合わせることで、SQL → 分析 → Slack通知 までの流れを自動化できました。AIが実装を代行し、人が設計を磨くそんなワークスタイルが自然に実現できたプロジェクトでした。

シロク エンジニアブログ

Discussion