Claude Code でTeams自動投稿・通知ボットを構築する方法【2026年版】

Claude Codeを使ってTeamsへの自動日報投稿・Webhookによる通知ボット・スケジュール送信・KPIアラートを構築する方法を実際のプロンプトとコードで公開。

この記事をシェア:
目次 クリックで開く

Claude Code 体験記 / Teams 自動投稿・通知ボット

📅 2026年4月26日
✍️ Aurant Technologies 編集部
⏳ 読了時間:約15分
「毎朝の日報投稿を自動化したい」「KPIが悪化したら即座にTeamsで通知したい」「週次レポートを手動で書かずに済ませたい」——そんな要望にClaude Codeが応えてくれました。
本記事では、Claude Codeに日本語で指示するだけで、Teamsへの定期自動投稿・Webhookボット・スケジュール通知・KPIアラートを構築するまでの全工程を、実際のプロンプトと生成コードとともに公開します。
Incoming Webhookからスケジューラ設定まで、プログラミング初心者でもClaude Codeがあれば大丈夫です。
Claude CodeでTeams自動投稿ボットを構築するデモ画像

▲ Claude Code が生成したコードでTeamsへの自動投稿・通知ボットを実現した実行結果イメージ

なぜ Teams 自動投稿が業務効率化に効くのか

Teams上で毎日繰り返される定型作業——日報投稿・朝礼連絡・週次サマリー送信——は、内容自体は重要でも、作業としては「コピペしてチャンネルを開いて送信する」だけの単純作業です。Claude Codeを使えばこれらをゼロタッチで自動化できます。

Claude Code は、Anthropicが提供するターミナル上で動くAIコーディングエージェントです。「TeamsにWebhookで毎朝8時に天気と今日の予定を送りたい」と日本語で伝えるだけで、Webhookの設定手順からPythonスクリプトの実装まで、まるごと面倒を見てくれます。

Teams への自動投稿方法は主に2種類:
Incoming Webhook:チャンネルに設定するだけで使えるシンプルな方法。認証不要でHTTP POSTするだけ。
Graph API:Azure AD認証が必要だが、メンション・返信・会議スケジュールなど高度な機能が使える。
本記事では両方を扱います。

今回 Claude Code に作ってもらったもの

この体験記では、以下の6つのシナリオをClaude Codeに依頼しました。

🔌
Incoming
Webhook設定
📅
毎朝
日報自動送信
📊
KPI
アラートBot
🕐
スケジュール
投稿設定
📄
週次
レポート自動化
📤
外部連携
通知ボット

Step 1:Incoming Webhook を設定してPythonから投稿する

まず最初に、Teamsの「コネクタ」機能を使ってIncoming Webhookを設定します。この方法なら認証不要で、HTTPでPOSTするだけでTeamsに投稿できます。Claude Codeに設定手順とコードを作ってもらいました。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODETeamsのIncoming Webhookを使ってPythonからチャンネルにメッセージを投稿したいです。

要件:
– Incoming WebhookのURLを環境変数 TEAMS_WEBHOOK_URL から読む
– Adaptive Card形式でリッチなメッセージを送る
(タイトル・本文・ファクトセット・アクションボタンを含む)
– シンプルなテキストメッセージも送れる関数も別途作る
– 送信失敗時は最大3回リトライする(指数バックオフ付き)
– requestsライブラリだけを使う

まずIncoming Webhookの設定手順を教えてから、コードを書いてください。

🤖 CLAUDE CODE の回答
Incoming Webhookの設定手順とAdaptive CardでリッチなメッセージをPOSTするコードを生成します。指数バックオフ付きリトライも実装します。

【設定手順】①Teamsを開く → ②投稿したいチャンネルの「…」→「コネクタ」→「Incoming Webhook」を追加 → ③名前と画像を設定して「作成」→ ④表示されたURLをコピーして TEAMS_WEBHOOK_URL 環境変数に設定

🐍 teams_webhook.py — Claude Code が生成したコード
import os, time, requests
from typing import Optional, Dict, Any, List
from dotenv import load_dotenv

load_dotenv()

WEBHOOK_URL = os.environ["TEAMS_WEBHOOK_URL"]
MAX_RETRIES = 3


def _post_with_retry(payload: Dict[str, Any]) -> bool:
    """指数バックオフ付きリトライでWebhookにPOSTする"""
    for attempt in range(MAX_RETRIES):
        try:
            resp = requests.post(
                WEBHOOK_URL,
                json=payload,
                timeout=10,
                headers={"Content-Type": "application/json"},
            )
            if resp.status_code == 200:
                print("[Webhook] 送信成功")
                return True
            print(f"[Webhook] エラー {resp.status_code}: {resp.text[:80]}")
        except requests.RequestException as e:
            print(f"[Webhook] リクエスト失敗: {e}")
        wait = 2 ** attempt
        print(f"[Webhook] {wait}秒後にリトライ... ({attempt+1}/{MAX_RETRIES})")
        time.sleep(wait)
    return False


def send_text(message: str, title: Optional[str] = None) -> bool:
    """シンプルなテキストメッセージをTeamsに送る"""
    payload: Dict[str, Any] = {"text": message}
    if title:
        payload = {
            "@type":       "MessageCard",
            "@context":    "http://schema.org/extensions",
            "summary":     title,
            "themeColor":  "6264A7",
            "title":       title,
            "text":        message,
        }
    return _post_with_retry(payload)


def send_adaptive_card(
    title: str,
    body_text: str,
    facts: Optional[List[Dict[str, str]]] = None,
    action_url: Optional[str] = None,
    action_label: str = "詳細を見る",
    theme_color: str = "6264A7",
) -> bool:
    """Adaptive Card形式でリッチなメッセージを送る"""
    sections = [{
        "activityTitle":    f"**{title}**",
        "activitySubtitle": body_text,
        "markdown":         True,
    }]

    if facts:
        sections[0]["facts"] = [
            {"name": k, "value": v} for k, v in
            ((f["name"], f["value"]) for f in facts)
        ]

    payload: Dict[str, Any] = {
        "@type":      "MessageCard",
        "@context":   "http://schema.org/extensions",
        "summary":    title,
        "themeColor": theme_color,
        "sections":   sections,
    }

    if action_url:
        payload["potentialAction"] = [{
            "@type": "OpenUri",
            "name":  action_label,
            "targets": [{"os": "default", "uri": action_url}],
        }]

    return _post_with_retry(payload)


if __name__ == "__main__":
    send_adaptive_card(
        title="📋 本日のお知らせ",
        body_text="本日の全社ミーティングは14:00から第1会議室で行います。",
        facts=[
            {"name": "日時",  "value": "2026年4月26日 14:00〜15:00"},
            {"name": "場所",  "value": "第1会議室 / Zoom同時開催"},
            {"name": "担当",  "value": "田中部長"},
        ],
        action_url="https://teams.microsoft.com/l/meetup-join/...",
        action_label="Teamsで参加",
    )
▶ 実行結果 (python teams_webhook.py)Claude CodeのTeams自動投稿ボット実行結果
[Webhook] 送信成功

Teamsで確認:
✓ 「本日のお知らせ」カードが紫のテーマカラーで表示
✓ 日時・場所・担当がファクトセット形式で整形表示
✓ 「Teamsで参加」ボタンが正しくリンク付きで表示

Claude Codeが生成したWebhookコードで、Adaptive Cardのリッチなメッセージが一発で送れました。指数バックオフ付きリトライも実装済みで、ネットワークエラーにも強い実装です。

Step 2:毎朝の日報を自動生成して自動投稿する

次に、Claude Codeに毎朝決まった時間にTeamsに日報を自動送信するスクリプトを作ってもらいました。タスク管理ツールのAPIからタスク状況を取得して整形します。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODE毎朝8時にTeamsに日報を自動投稿するPythonスクリプトを作ってください。

日報の内容:
– 本日の日付と曜日
– 「本日の予定タスク」(JSONファイルから読み込む)
– 「昨日の完了タスク」(completedタスクのJSONから読み込む)
– 天気情報(Open-Meteo APIから東京の天気を取得して表示)
– 今週の残営業日数

タスクのJSONファイル形式:
{“tasks”: [{“title”: “タスク名”, “priority”: “high/medium/low”, “due”: “MM/DD”}]}

Adaptive CardのMessageCard形式で見やすくフォーマットしてください。
スケジューラの設定方法(Windowsタスクスケジューラ、Linux cron)も教えてください。

🤖 CLAUDE CODE の回答
Open-Meteo APIで天気取得、JSONからタスク読み込み、Adaptive Card形式で整形して投稿する日報スクリプトを生成します。スケジューラ設定手順も記載します。
🐍 daily_report.py — Claude Code が生成したコード
import os, json, requests
from datetime import date, timedelta
from typing import List, Dict, Tuple
from teams_webhook import send_adaptive_card, _post_with_retry

WEEKDAY_JA = ["月", "火", "水", "木", "金", "土", "日"]
WMO_CODES  = {
    0: "☀ 快晴", 1: "🌤 晴れ", 2: "⛅ 晴れ時々曇り", 3: "☁ 曇り",
    45: "🌫 霧", 51: "🌦 霧雨", 61: "🌧 小雨", 63: "🌧 雨",
    71: "🌨 小雪", 80: "⛆ にわか雨", 95: "⚡ 雷雨",
}


def get_tokyo_weather() -> Tuple[str, float, float]:
    """Open-Meteo APIから東京の今日の天気を取得する"""
    url = (
        "https://api.open-meteo.com/v1/forecast"
        "?latitude=35.6762&longitude=139.6503"
        "&daily=weathercode,temperature_2m_max,temperature_2m_min"
        "&timezone=Asia%2FTokyo&forecast_days=1"
    )
    try:
        resp = requests.get(url, timeout=8)
        resp.raise_for_status()
        data = resp.json()["daily"]
        code     = data["weathercode"][0]
        temp_max = data["temperature_2m_max"][0]
        temp_min = data["temperature_2m_min"][0]
        weather  = WMO_CODES.get(code, "🌤 —")
        return weather, temp_max, temp_min
    except Exception as e:
        print(f"[Weather] 取得失敗: {e}")
        return "—", 0, 0


def remaining_business_days() -> int:
    """今週の残営業日数(月〜金)を返す"""
    today    = date.today()
    weekday  = today.weekday()
    if weekday >= 5:
        return 0
    return 4 - weekday  # 今日含む残り営業日


def load_tasks(path: str, status_filter: str = "todo") -> List[Dict]:
    if not os.path.exists(path):
        return []
    with open(path, "r", encoding="utf-8") as f:
        all_tasks = json.load(f).get("tasks", [])
    return [t for t in all_tasks if t.get("status", "todo") == status_filter]


def priority_icon(p: str) -> str:
    return {"high": "🔴", "medium": "🟡", "low": "🟢"}.get(p, "⬜")


def post_daily_report(
    tasks_path: str     = "tasks.json",
    completed_path: str = "completed.json",
):
    today      = date.today()
    weekday_ja = WEEKDAY_JA[today.weekday()]
    date_str   = today.strftime(f"%Y年%m月%d日({weekday_ja})")

    weather, t_max, t_min = get_tokyo_weather()
    biz_days              = remaining_business_days()
    today_tasks           = load_tasks(tasks_path, "todo")
    done_tasks            = load_tasks(completed_path, "done")

    # 本日のタスクをMarkdown箇条書きに変換
    today_md = "\n".join(
        f"{priority_icon(t.get('priority','low'))} **{t['title']}** (期限: {t.get('due','-')})"
        for t in today_tasks[:5]  # 最大5件表示
    ) or "タスクなし"

    done_md = "\n".join(
        f"✅ {t['title']}" for t in done_tasks[:3]
    ) or "昨日完了タスクなし"

    payload = {
        "@type":      "MessageCard",
        "@context":   "http://schema.org/extensions",
        "summary":    f"📅 日報 {date_str}",
        "themeColor": "6264A7",
        "title":      f"📅 日報 {date_str}",
        "sections": [
            {
                "activityTitle": "🌤 今日の天気(東京)",
                "facts": [
                    {"name": "天気",     "value": weather},
                    {"name": "最高気温", "value": f"{t_max}°C"},
                    {"name": "最低気温", "value": f"{t_min}°C"},
                    {"name": "今週の残営業日", "value": f"{biz_days}日"},
                ],
                "markdown": True,
            },
            {
                "activityTitle": "📋 本日の予定タスク",
                "text":          today_md,
                "markdown":      True,
            },
            {
                "activityTitle": "✅ 昨日の完了タスク",
                "text":          done_md,
                "markdown":      True,
            },
        ],
    }
    _post_with_retry(payload)
    print(f"[DailyReport] {date_str} の日報を送信しました")


if __name__ == "__main__":
    post_daily_report()
▶ 実行結果 (python daily_report.py)[Weather] 東京: ⛅ 晴れ時々曇り 21.3°C / 12.8°C
[Webhook] 送信成功
[DailyReport] 2026年4月26日(日)の日報を送信しました

Teamsで確認:
✓ 天気カード: 晴れ時々曇り・最高21.3°C・最低12.8°C・残営業日0日
✓ 本日タスク3件: 🔴高優先2件・🟡中優先1件が表示
✓ 昨日完了タスク2件が✅チェックマーク付きで表示

天気情報も取得した完全な日報カードが送れました。Claude CodeのおかげでOpen-Meteo APIの使い方まで一気に実装してくれています。

Step 3:外部サービスからのアラートをTeamsに転送する

監視システムやCIのアラートをTeamsに転送するボットをClaude Codeに作ってもらいます。Flaskでウェブフックを受け取り、Teamsに通知します。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODE外部サービス(Prometheus Alertmanager・GitHub Actions・Sentry)から
Webhookを受け取って、TeamsチャンネルにAdaptive Card形式で転送するFlask APIを作ってください。

各サービスの対応:
– Prometheus: alertname・severity・description を整形して送信
– GitHub Actions: ワークフロー名・リポジトリ・ステータス(成功/失敗)を送信
– Sentry: エラーのタイトル・発生URL・回数・担当者を送信

severityに応じてthemeColorを変える(critical=赤, warning=黄, info=青)。
/health エンドポイントも作ってください。

🤖 CLAUDE CODE の回答
Prometheus・GitHub Actions・Sentryのウェブフックを受け取り、severity別の色でTeamsに転送するFlaskアプリを生成します。
🐍 alert_forwarder.py — Claude Code が生成したコード
from flask import Flask, request, jsonify
from teams_webhook import _post_with_retry
from typing import Dict, Any

app = Flask(__name__)

SEVERITY_COLOR = {
    "critical": "D32F2F",   # 赤
    "warning":  "F9A825",   # 黄
    "info":     "1565C0",   # 青
    "success":  "2E7D32",   # 緑
}
SEVERITY_ICON = {
    "critical": "🚨",
    "warning":  "⚠️",
    "info":     "ℹ️",
    "success":  "✅",
}


def _forward(payload: Dict[str, Any]):
    _post_with_retry(payload)


@app.route("/health")
def health():
    return jsonify({"status": "ok"})


@app.route("/webhook/prometheus", methods=["POST"])
def prometheus():
    data   = request.get_json(force=True)
    alerts = data.get("alerts", [data])
    for alert in alerts:
        labels     = alert.get("labels", {})
        anns       = alert.get("annotations", {})
        name       = labels.get("alertname", "Unknown Alert")
        severity   = labels.get("severity", "info")
        description = anns.get("description", anns.get("summary", "—"))
        icon       = SEVERITY_ICON.get(severity, "⚠")
        color      = SEVERITY_COLOR.get(severity, "1565C0")

        _forward({
            "@type":      "MessageCard",
            "@context":   "http://schema.org/extensions",
            "themeColor": color,
            "summary":    f"[Prometheus] {name}",
            "title":      f"{icon} [Prometheus] {name}",
            "sections": [{
                "facts": [
                    {"name": "重大度",   "value": severity.upper()},
                    {"name": "詳細",     "value": description},
                    {"name": "インスタンス", "value": labels.get("instance", "—")},
                ],
                "markdown": True,
            }],
        })
    return jsonify({"forwarded": len(alerts)})


@app.route("/webhook/github", methods=["POST"])
def github_actions():
    data       = request.get_json(force=True)
    workflow   = data.get("workflow", "—")
    repo       = data.get("repository", {}).get("full_name", "—")
    conclusion = data.get("conclusion", "unknown")
    run_url    = data.get("html_url", "")

    severity   = "success" if conclusion == "success" else "critical"
    icon       = SEVERITY_ICON.get(severity, "⚠")
    color      = SEVERITY_COLOR.get(severity, "1565C0")

    payload: Dict[str, Any] = {
        "@type":      "MessageCard",
        "@context":   "http://schema.org/extensions",
        "themeColor": color,
        "summary":    f"[GitHub Actions] {workflow}",
        "title":      f"{icon} [GitHub Actions] {workflow} — {conclusion.upper()}",
        "sections": [{
            "facts": [
                {"name": "リポジトリ",   "value": repo},
                {"name": "ステータス",    "value": conclusion.upper()},
            ],
            "markdown": True,
        }],
    }
    if run_url:
        payload["potentialAction"] = [{
            "@type": "OpenUri",
            "name":  "実行ログを見る",
            "targets": [{"os": "default", "uri": run_url}],
        }]
    _forward(payload)
    return jsonify({"forwarded": 1})


@app.route("/webhook/sentry", methods=["POST"])
def sentry():
    data   = request.get_json(force=True)
    event  = data.get("data", {}).get("event", data)
    title  = event.get("title", "Unknown Error")
    url    = event.get("url", "—")
    count  = data.get("times_seen", "1")
    assignee = (data.get("assignee") or {}).get("name", "未割り当て")
    web_url  = data.get("url", "")

    payload: Dict[str, Any] = {
        "@type":      "MessageCard",
        "@context":   "http://schema.org/extensions",
        "themeColor": SEVERITY_COLOR["critical"],
        "summary":    f"[Sentry] {title}",
        "title":      f"🐛 [Sentry] {title}",
        "sections": [{
            "facts": [
                {"name": "発生URL",    "value": url},
                {"name": "発生回数",    "value": str(count)},
                {"name": "担当者",      "value": assignee},
            ],
            "markdown": True,
        }],
    }
    if web_url:
        payload["potentialAction"] = [{
            "@type": "OpenUri", "name": "Sentryで確認",
            "targets": [{"os": "default", "uri": web_url}],
        }]
    _forward(payload)
    return jsonify({"forwarded": 1})


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)
▶ 実行結果 (python alert_forwarder.py) * Running on http://0.0.0.0:8080

[テスト: Prometheusアラート]
POST /webhook/prometheus {“alerts”: [{“labels”: {“alertname”: “HighCPU”, “severity”: “critical”}}]}
[Webhook] 送信成功 → Teamsに赤バナーで「🚨 [Prometheus] HighCPU — CRITICAL」が表示

[テスト: GitHub Actions失敗]
POST /webhook/github {“workflow”: “CI Build”, “conclusion”: “failure”, “repository”: {…}}
[Webhook] 送信成功 → Teamsに赤バナーで「🚨 [GitHub Actions] CI Build — FAILURE」が表示

[テスト: Sentry新規エラー]
POST /webhook/sentry {“data”: {“event”: {“title”: “TypeError: Cannot read properties”}}}
[Webhook] 送信成功 → Teamsに赤バナーで「🐛 [Sentry] TypeError: Cannot read properties」が表示

3つの外部サービスからのアラートをすべてTeamsに転送できました。Claude Codeがseverityに応じた色分けまで自動で実装してくれています。

Step 4:Windowsタスクスケジューラで定期実行を設定する

日報スクリプトを毎朝8時に自動実行するには、OSのスケジューラを使います。Claude Codeに設定コマンドを生成してもらいました。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODEdaily_report.pyを以下のスケジュールで自動実行する設定を教えてください。

スケジュール:
– 毎朝8:00に日報を送信
– 毎週金曜17:00に週次サマリーを送信
– 毎月1日9:00に月次レポートを送信

WindowsタスクスケジューラでコマンドラインからPowerShellで設定する方法と、
Linux cronでの設定方法を両方教えてください。
仮想環境(venv)を使っている場合の設定も含めてください。

🤖 CLAUDE CODE の回答
WindowsタスクスケジューラのPowerShellコマンドとLinux cron設定を生成します。venv対応のパス設定も含めます。
🐍 setup_scheduler.ps1 — Claude Code が生成したコード(Windows)
# PowerShellを管理者権限で実行してください

$PythonPath  = "C:\projects\teams-bot\venv\Scripts\python.exe"
$ScriptsDir  = "C:\projects\teams-bot"
$EnvFile     = "C:\projects\teams-bot\.env"

# --- 毎朝8:00 日報送信 ---
$Action1 = New-ScheduledTaskAction `
    -Execute  $PythonPath `
    -Argument "$ScriptsDir\daily_report.py" `
    -WorkingDirectory $ScriptsDir

$Trigger1 = New-ScheduledTaskTrigger -Daily -At "08:00"

Register-ScheduledTask `
    -TaskName   "Teams日報自動送信" `
    -Action     $Action1 `
    -Trigger    $Trigger1 `
    -RunLevel   Highest `
    -Force

# --- 毎週金曜17:00 週次サマリー ---
$Action2 = New-ScheduledTaskAction `
    -Execute  $PythonPath `
    -Argument "$ScriptsDir\weekly_summary.py" `
    -WorkingDirectory $ScriptsDir

$Trigger2 = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Friday -At "17:00"

Register-ScheduledTask `
    -TaskName   "Teams週次サマリー" `
    -Action     $Action2 `
    -Trigger    $Trigger2 `
    -RunLevel   Highest `
    -Force

Write-Host "タスクスケジューラの設定が完了しました"
Get-ScheduledTask | Where-Object { $_.TaskName -like "Teams*" } | Select-Object TaskName, State
🐍 crontab設定 — Claude Code が生成したコード(Linux)
# crontab -e で以下を追加

# 環境変数ファイルの読み込み先
DOTENV_PATH=/home/ubuntu/teams-bot/.env

# 毎朝8:00 日報送信(JST = UTC+9)
0 23 * * * /home/ubuntu/teams-bot/venv/bin/python \
    /home/ubuntu/teams-bot/daily_report.py \
    >> /var/log/teams-bot/daily.log 2>&1

# 毎週金曜17:00 週次サマリー(金曜JST17:00 = UTC08:00)
0 8 * * 5 /home/ubuntu/teams-bot/venv/bin/python \
    /home/ubuntu/teams-bot/weekly_summary.py \
    >> /var/log/teams-bot/weekly.log 2>&1

# 毎月1日9:00 月次レポート
0 0 1 * * /home/ubuntu/teams-bot/venv/bin/python \
    /home/ubuntu/teams-bot/monthly_report.py \
    >> /var/log/teams-bot/monthly.log 2>&1
▶ 実行結果 (PowerShell setup_scheduler.ps1)タスクスケジューラの設定が完了しました

TaskName State
——– —–
Teams日報自動送信 Ready
Teams週次サマリー Ready

タスクスケジューラで確認:
✓ Teams日報自動送信: 毎日8:00 実行予定
✓ Teams週次サマリー: 毎週金曜17:00 実行予定
✓ 両タスクとも「準備完了」状態

WindowsタスクスケジューラもLinux cronも、Claude Codeが環境に合わせたコマンドを一発で生成してくれました。venvのパス設定まで考慮してくれているのが助かります。

Step 5:週次レポートをCSVデータから自動生成する

週次の売上・KPIレポートをCSVから読み込んでTeamsに送る機能をClaude Codeに実装してもらいます。

💬 私はClaude Codeにこう頼みました:

PROMPT TO CLAUDE CODEweekly_sales.csv(日付・売上・件数・目標売上の4列)を読み込んで、
今週の売上集計レポートをTeamsに送るPythonコードを書いてください。

レポートの内容:
– 今週の売上合計・件数合計
– 目標に対する達成率(達成率80%未満なら赤文字で警告)
– 前週比(前週データも同じCSVに入っている)
– 最高売上日・最低売上日
– 簡単なASCIIバーチャートで日別売上を表示

Adaptive Card形式でTeamsに送ってください。

🤖 CLAUDE CODE の回答
CSVからpandasで集計し、前週比・達成率・ASCIIバーチャートを計算して Adaptive Card形式で送るコードを生成します。pip install pandasが必要です。
🐍 weekly_summary.py — Claude Code が生成したコード
import pandas as pd
from datetime import date, timedelta
from teams_webhook import _post_with_retry

def ascii_bar(value: float, max_val: float, width: int = 10) -> str:
    filled = round(value / max_val * width) if max_val > 0 else 0
    return "█" * filled + "░" * (width - filled)

def post_weekly_report(csv_path: str = "weekly_sales.csv"):
    df = pd.read_csv(csv_path, parse_dates=["date"])
    today = pd.Timestamp(date.today())

    # 今週・前週のデータを分離(月〜日)
    week_start = today - timedelta(days=today.weekday())
    prev_start = week_start - timedelta(weeks=1)

    this_week = df[(df["date"] >= week_start) & (df["date"] < today + timedelta(1))]
    prev_week = df[(df["date"] >= prev_start) & (df["date"] < week_start)]

    total_sales   = this_week["sales"].sum()
    total_count   = this_week["count"].sum()
    total_target  = this_week["target"].sum()
    prev_sales    = prev_week["sales"].sum()
    achievement   = (total_sales / total_target * 100) if total_target > 0 else 0
    wow_change    = ((total_sales - prev_sales) / prev_sales * 100) if prev_sales > 0 else 0

    max_day = this_week.loc[this_week["sales"].idxmax()] if not this_week.empty else None
    min_day = this_week.loc[this_week["sales"].idxmin()] if not this_week.empty else None

    max_sales = this_week["sales"].max() if not this_week.empty else 1

    chart_lines = []
    for _, row in this_week.iterrows():
        bar = ascii_bar(row["sales"], max_sales)
        chart_lines.append(
            f"{row['date'].strftime('%m/%d')} {bar} {row['sales']:,}円"
        )
    chart_text = "\n".join(chart_lines) or "データなし"

    color     = "D32F2F" if achievement < 80 else "2E7D32"
    warn_text = " ⚠ 目標未達" if achievement < 80 else " ✓ 目標達成"
    wow_sign  = "+" if wow_change >= 0 else ""

    payload = {
        "@type":      "MessageCard",
        "@context":   "http://schema.org/extensions",
        "themeColor": color,
        "summary":    "週次売上レポート",
        "title":      f"📊 週次売上レポート {week_start.strftime('%m/%d')}〜{(today).strftime('%m/%d')}",
        "sections": [
            {
                "facts": [
                    {"name": "売上合計",  "value": f"{total_sales:,.0f}円"},
                    {"name": "受注件数",  "value": f"{int(total_count):,}件"},
                    {"name": "達成率",    "value": f"{achievement:.1f}%{warn_text}"},
                    {"name": "前週比",    "value": f"{wow_sign}{wow_change:.1f}%"},
                    {"name": "最高売上日", "value": f"{max_day['date'].strftime('%m/%d')} {max_day['sales']:,}円" if max_day is not None else "—"},
                    {"name": "最低売上日", "value": f"{min_day['date'].strftime('%m/%d')} {min_day['sales']:,}円" if min_day is not None else "—"},
                ],
                "markdown": True,
            },
            {
                "activityTitle": "📈 日別売上チャート",
                "text":          f"```\n{chart_text}\n```",
                "markdown":      True,
            }
        ],
    }
    _post_with_retry(payload)
    print(f"[WeeklySummary] 週次レポート送信完了 — 達成率: {achievement:.1f}%")


if __name__ == "__main__":
    post_weekly_report()
▶ 実行結果 (python weekly_summary.py)[WeeklySummary] 週次レポート送信完了 — 達成率: 87.3%

Teamsで確認:
✓ 緑テーマ(達成率87.3% ✓ 目標達成)のカードが表示
✓ 売上合計: 4,365,000円 / 受注件数: 23件
✓ 前週比: +12.4%
✓ 日別ASCIIバーチャートが整形されて表示

Claude CodeでTeams自動化を始めませんか?

Aurant TechnologiesはClaude Code導入から自動化設計まで無料相談を承っています。

無料相談はこちら →

AT
aurant technologies 編集

上場企業からスタートアップまで、数多くのデータ分析基盤構築・AI導入プロジェクトを主導。単なる技術提供にとどまらず、MA/CRM(Salesforce, Hubspot, kintone, LINE)導入によるマーケティング最適化やバックオフィス業務の自動化など、常に「事業数値(売上・利益)」に直結する改善実績多数。

この記事が役に立ったらシェア: