クローズドベータ(月¥5,000予算)を安全に運用するために、2つの仕組みが必要:
どちらも既存テーブル(reminders / users)から集計。新テーブルは作らない。
src/budget.ts)新ファイル src/budget.ts + src/budget.test.ts
// 定数
export const CALL_COST_YEN = 13;
export const NUMBER_MONTHLY_FEE_YEN = 165;
// 月間コスト = リマインダー通話数 × ¥13 + ¥165
export function calculateMonthlyCost(reminderCalls: number): number
// コスト >= 実効予算 なら超過
// 実効予算 = budgetYen × SAFETY_MARGIN_RATIO(認証コール分のバッファ)
export const SAFETY_MARGIN_RATIO = 0.8;
export function isBudgetExceeded(cost: number, budgetYen: number): boolean
認証コールの扱い: verification call は既存データから正確にカウントできない(verification_request_count は認証成功時にリセットされ、再試行や番号変更も追跡不能)。新テーブル不要の方針のため、SAFETY_MARGIN_RATIO = 0.8(予算の20%を認証コール用に留保)で安全側に倒す。Admin stats では tracked(リマインダー通話のみ)と effective budget(80%適用後)を明示する。
テストケース:
変更ファイル: src/index.ts, wrangler.toml
MONTHLY_BUDGET_YEN env var を追加(wrangler.toml の [vars] に "5000")SELECT COUNT(*) FROM reminders
WHERE status IN ('calling','completed','unanswered')
AND called_at >= ? -- 当月1日 0:00 JST の unix timestamp
isPhoneNumber(text) 分岐の先頭でチェック。超過時は認証コールを発信せず「現在新規受付を停止しています」と返信isUnlimited ロジックを拡張)/admin/stats エンドポイント(認証 + バジェット JSON)変更ファイル: src/index.ts
ADMIN_API_KEY は wrangler secret put で設定(wrangler.toml にコミットしない)worker-configuration.d.ts の Env に ADMIN_API_KEY を手動追加(wrangler types では secret は出力されない)GET /admin/stats エンドポイント追加Authorization: Bearer <ADMIN_API_KEY>{
"budget": {
"reminderCalls": 42,
"trackedCost": 711,
"budgetTotal": 5000,
"effectiveBudget": 4000,
"remaining": 3289,
"exceeded": false
}
}
trackedCost: リマインダー通話のみ(認証コールは含まない)effectiveBudget: budgetTotal × 0.8(認証コール用20%留保後)remaining: effectiveBudget - trackedCost/admin/stats にファネルメトリクス追加変更ファイル: src/index.ts
既存テーブルから集計するクエリ:
| メトリクス | 説明 | クエリ |
|---|---|---|
| phoneSubmitted | 電話番号を送信したユーザー数 | SELECT COUNT(*) FROM users |
| verified | 電話認証を完了したユーザー数 | SELECT COUNT(*) FROM users WHERE phone_verified = 1 |
| hasUsed | 1回以上リマインダーを作成したユーザー数 | SELECT COUNT(DISTINCT line_user_id) FROM reminders |
| retainedD7 | 登録7日以上前のユーザーで、登録日+7日以降にもリマインダーを作成した数 | 下記参照 |
| quotaReached | 無料枠(3回)に到達したユーザー数(オーナー/VIP除外) | 下記参照 |
注意: phoneSubmitted は友達追加しただけのユーザーを含まない(users テーブルは電話番号送信時に初めてINSERTされるため)。真のファネル起点(友達追加数)の計測には follow イベントのハンドリングが必要(Phase 2a 機能実装の「LINE イベント型の厳密化」で対応予定)。
quotaReached クエリ(実際の制限ロジックと完全に一致):
実際のコード: effectiveCalls = (month_key === currentMonth ? calls_this_month : 0) + pendingCount >= 3
→ 月跨ぎ時は calls_this_month を0にリセット。オーナー/VIPは免除。
quotaReached は JS で計算する(オーナー/VIPのフィルタリングに env var が必要なため、純SQLでは完結しない):
effectiveCalls + pendingCount >= 3 でフィルタレスポンスに funnel フィールドを追加:
{
"budget": { ... },
"funnel": {
"phoneSubmitted": 50,
"verified": 35,
"hasUsed": 28,
"retainedD7": 12,
"quotaReached": 8
}
}
| ファイル | 役割 |
|---|---|
src/index.ts |
webhook + cron + admin エンドポイント |
src/index.test.ts |
統合テスト |
src/budget.ts |
バジェット計算純関数(新規) |
src/budget.test.ts |
バジェット計算テスト(新規) |
wrangler.toml |
env var 追加 |
schema.sql |
変更なし |
verification_request_count は認証成功時にリセットされ、再試行・番号変更も既存データから正確にカウントできない。SAFETY_MARGIN_RATIO = 0.8 で予算の20%を留保し安全側に倒す(Codex P1 指摘対応)calls_this_month + pending >= 3)と一致させる(Codex P2 指摘対応)getCurrentMonthKey() と同じロジック)wrangler secret put で設定。wrangler.toml にコミットしないtsc --noEmit && pnpm test
全4イテレーション完了後、~/bin/codex-full review で Codex レビュー → LGTM まで修正。