Appearance
日次スケジュールサマリ通知
日程調整の抜け漏れを拾い直すため、業務側チャンネルへ毎朝サマリを流す仕組み。
通知内容
- 提案から3営業日以上動きのない日程調整 — 企業へ提案したまま確定していない調整
- 本日の確定予定 — その日に予定されている面接など
メンションは付けない。毎朝必ず流れる定期便で名指しすると通知疲れを起こし、 かえって読まれなくなるため。
該当0件でも送る。無音だと「バッチが動いていないのか、本当に0件なのか」が 区別できず、結局誰も信用しなくなる。
見え方
Block Kit で組み、滞留と本日の確定予定を別々の attachment に分けている。 色は attachment 単位でしか付けられないため、1枚にまとめると滞留の重さを色で 表せなくなるからである。
:memo: [BUSON] 日次スケジュールサマリ 2026/08/26(水)|滞留 2件・本日の確定 2件
┃ 警告 提案から3営業日以上動きのない日程調整(2件) ← 赤バー
┃ 赤 山田 太郎 / 株式会社サンプル / 一次面接
┃ 提案 08/17(月)(6営業日経過) ・ 候補2枠(候補日はすべて経過・要再提案)
┃ 黄 田中 次郎 / 株式会社テスト / 二次面接
┃ 提案 08/21(金)(3営業日経過) ・ 候補1枠(最短 09/01(火) 11:00)
┃ 予定 本日の確定予定(2件) ← 青バー
┃ `10:00〜11:00` 佐藤 花子 / 株式会社サンプル / 最終面接
┃ `14:00〜15:00` 鈴木 一郎 / 株式会社テスト / 一次面接- 見出し行(プッシュ通知にもそのまま出る)に件数まで載せる。attachment の中身は プッシュ通知・チャンネル一覧のプレビューに出ないため、この1行だけで 「今日は対応が必要か」を判断できるようにしている
- 求職者名は管理画面の求職者詳細へのリンクになる(
APP_BASE_URL未設定なら素の文字列) - 「最短 …」はまだ到来していない候補日のうち最も早いもの。候補日がすべて過ぎた 調整は「候補日はすべて経過・要再提案」と出す(過去日付を「最短」と出すと 予約できる枠が残っているように読めるため)
- 該当0件の日はヘッダー1行(✅ / 📅)だけに畳む
色と信号
| 状態 | attachment の色 | 行頭の絵文字 |
|---|---|---|
| 滞留なし | 緑 #2eb886 | — |
| 3〜4営業日の滞留 | 黄 #e2b203 | :large_yellow_circle: |
| 5営業日以上の滞留が1件でもある | 赤 #d92d20 | :red_circle: |
| 本日の確定予定(常に情報色) | 青 #2f6fed | — |
attachment の色は「その日で最も重い滞留」で決まる。行頭の信号を併記しているのは、 色が出ない通知プレビューでも重さが読めるようにするため。
文字数上限への対処
Slack の section は 3000 字が上限で、超えるとメッセージが丸ごと 400 で弾かれる。 それは「通知が消える」ことを意味する(このバッチが防ごうとしている失敗そのもの)ため、 一覧は文字数予算で複数セクションに分割する。黙って切り詰めることはしない。
なぜ「空き」ではなく「提案」を見るのか
提案 は空き行とは別の新規行として作られ、空き行はDB上で分割も消費もされない (フロントの splitAvailableSchedulesByOccupied が表示上だけ重なりを差し引いている)。
そのため「空きのまま提案されていない」を行単位で検知すると、次のケースで永久に 鳴り続ける。
- 求職者が5時間の空きを入れ、そのうち1時間だけを提案した
- 3日分の空きのうち2日分だけを提案した
業務側が実際に動いた記録である 提案 行を起点にすることで、この誤検知を構造的に 避けている。
なお、「空きが登録されたが一度も提案されていない」はこのサマリでは拾えない。 その入口は /mypage の空き時間登録リアルタイム通知が担う (E2E手順 の「5.」を参照)。
滞留判定の詳細
- 単位: 選考(
application_id) × ステージ(stage_id)。1回の調整で候補日程を複数 提案するのが通常運用なので、行単位で出すと同じ調整が何行にも増殖する - 営業日: 土日を除外して数える。祝日マスタが存在しないため祝日は考慮しない (3営業日の閾値が最大1〜2日ずれる。アラート用途では許容範囲と判断)
- 起点:
created_at(提案した時点)とupdated_at(最後に動いた時点)の両方が 閾値を超えていること。updated_atも見るのは、確定→提案への差し戻し (リスケの正規手順)直後の行が古いcreated_atのせいで即座に鳴るのを防ぐため - DB側の絞り込み: 暦日ベースのプレフィルタで母集合を絞り、営業日の厳密な判定は アプリ側で行う。N営業日の経過は必ずN暦日以上の経過を伴うので取りこぼしは出ない
- 表示上限: 15件。超過分は
…ほかN件として件数を明示する(黙って打ち切らない)
実行方法
Cloud Scheduler から認証付きで叩く。
POST /api/v1/notifications/daily-schedule-summary
Authorization: Bearer ${API_SERVICE_KEY}スケジュール: 平日の朝9時(0 9 * * 1-5 / タイムゾーン Asia/Tokyo)。
Cloud Scheduler ジョブは CI が作る
api-v* タグを打つと、deploy-api.yml の sync-scheduler ジョブが daily-schedule-summary を 作成または更新 する。手作業でのジョブ作成は不要。
- スケジュールを変えたい:
deploy-api.ymlのSCHEDULEを書き換えてapi-v*タグを打つ。コンソールを触らないので変更が履歴に残る - 宛先URL: Cloud Run の実URL(
*.run.app)をgcloud run services describeで 取得して使う。Cloudflare Access 配下のドメインに投げると Access に弾かれるため - API のデプロイとは別ジョブにしてある。権限不足などでスケジューラ同期が失敗しても API のデプロイ自体は完了済みになる(ワークフローは赤くなるので気づける)
- リトライは上限付き(3回 / 最大15分)。無制限だと、実際には成功していた実行が タイムアウト扱いになったときSlackへ何通も出てしまう
一度だけ手動が必要
デプロイ用サービスアカウント(GCP_SERVICE_ACCOUNT)に roles/cloudscheduler.admin を付与しておくこと。この付与だけは CI から実行できない (自分自身に権限を付けることはできないため)。Cloud Scheduler API の有効化も同様。
bash
gcloud services enable cloudscheduler.googleapis.com --project buson-482306
gcloud projects add-iam-policy-binding buson-482306 \
--member "serviceAccount:<GCP_SERVICE_ACCOUNT>" \
--role roles/cloudscheduler.admin既存の enrich / crawler 用スケジューラ(docs/operations/cloud-run-setup.md)は 従来どおり手動作成のまま。CI 管理はこのジョブのみ。
レスポンス
json
{
"data": {
"staleProposalCount": 1,
"todayConfirmedCount": 2,
"notified": true,
"skipped": false
}
}notified: true= Slack送信成功skipped: true= Webhook URL未設定などで送信未実行
内容だけ確認する(送信しない)
?dry_run=true を付けると、Slackへ送らずに内容を返す。素のテキスト版が message、 実際に送るペイロードが payload。
bash
# 内容をひと目で確認する
curl -s -X POST \
"http://127.0.0.1:8787/api/v1/notifications/daily-schedule-summary?dry_run=true" \
-H "Authorization: Bearer ${API_SERVICE_KEY}" | jq -r '.data.message'
# 見た目を確認する(出力を Block Kit Builder に貼る)
curl -s -X POST \
"http://127.0.0.1:8787/api/v1/notifications/daily-schedule-summary?dry_run=true" \
-H "Authorization: Bearer ${API_SERVICE_KEY}" | jq '.data.payload'Block Kit Builder: https://app.slack.com/block-kit-builder
ローカル開発(NODE_ENV != production)では認証がダミーユーザーで通るため、 Authorization ヘッダなしでも実行できる。
関連環境変数
| 変数名 | 説明 |
|---|---|
SLACK_SCHEDULE_WEBHOOK_URL | 送信先(未設定なら SLACK_WEBHOOK_URL にフォールバック) |
APP_BASE_URL | 求職者詳細リンクの組み立てに使う(未設定ならリンク行を省略) |
API_SERVICE_KEY | Cloud Scheduler からのサービス間通信認証(authMiddleware が検証) |
SLACK_SCHEDULE_MENTION_USER_IDS はこのサマリでは使わない(メンションしないため)。