<!-- UPDATED_AT --> 2026-05-10
職員が記録保存後に「家族へ通知」を選んだとき、即時に連絡帳へ送らず、家族ユーザーの過去30日の閲覧時間帯から算出した「届きやすい時間」に family_notification_logs へスケジュールし、Vercel Cron が到来時に contact_messages へ挿入する流れです。外部AI(Gemini)は使用しません(閲覧ログの集計とルールのみ)。
---
api/lib/optimalNotification.js(閲覧履歴の時間帯スコア、データ不足時は即時相当にフォールバック)POST /api/notify-family(api/notify-family.js)… family_notification_logs INSERT、team_messages 職員用通知POST /api/optimal-notification(api/optimal-notification.js)GET /api/family-notification-dispatch(api/family-notification-dispatch.js)… scheduled_for <= now かつ未送信行を処理し contact_messages へ反映後 notified_at を更新api/save-staff-action.js から notify-family を呼ぶ経路(家族通知オプションが有効なとき)src/js/pages/family.js・src/js/data/store.js(閲覧ログ送信・通知ログ参照など)---
supabase/migrations/20260510120000_family_notification_optimization.sqldocs/sql/family_notification_optimization.sql(マイグレと同趣旨の説明付き)主なオブジェクト:
family_notification_logs(scheduled_for, notified_at, tenant_id, record_id 等)---
vercel dev)SUPABASE_URL または VITE_SUPABASE_URLSUPABASE_SERVICE_ROLE_KEY … notify-family の service 経路 INSERT、family-notification-dispatch の一括処理に使用VITE_SUPABASE_ANON_KEY … notify-family が職員 JWT と併用するクライアント用キーCRON_SECRET(推奨)… family-notification-dispatch も Authorization: Bearer <CRON_SECRET> が一致しないと 401(未設定時はスキップのため本番では推奨)---
vercel.json)| パス | スケジュール(UTC) | 備考 |
|------|---------------------|------|
| /api/family-notification-dispatch | 30 21 * * * | 1日1回(Hobby プランでは「1日複数回」の Cron が拒否されるため。高頻度が必要なら Pro プランまたは外部スケジューラを検討) |
Cron から呼ぶときは Vercel 側で CRON_SECRET が設定されていれば、リクエストに同じ Bearer が付与される想定です。
---
1. マイグレ適用済みであること(family_notification_logs が存在)
2. 職員で記録保存 → 家族通知をトリガー(UI または save-staff-action 経路)
3. Supabase Table Editor で family_notification_logs に行が追加され、scheduled_for が未来時刻になっていること
4. GET /api/family-notification-dispatch を CRON_SECRET 付きで実行(または Cron 実行後)、該当行の notified_at が埋まり、contact_messages に家族向けメッセージが増えること
curl 例(本番 URL とシークレットは置き換え):
curl -sS -H "Authorization: Bearer YOUR_CRON_SECRET" "https://(本番ドメイン)/api/family-notification-dispatch"
---
---
SETUP_PRODUCTION.md](./SETUP_PRODUCTION.md)DOCUMENTATION_INDEX.md](./DOCUMENTATION_INDEX.md)