現在のタスク自動生成システムは、全商材に対して一律に毎日タスクを生成している。しかし広告運用の現場では、データ量が少ない商材に対して毎日アラートを出しても統計的に意味のある判断ができない。予算の大小ではなく、データボリューム(クリック数・インプレッション数)の統計的有意性に基づいてタスク生成頻度を制御すべきである。
Vercel Cron で毎日 22:00 UTC(翌日 JST 7:00)に実行される。
// apps/ops/vercel.json
{
"crons": [
{ "path": "/api/cron/sync-ads", "schedule": "0 20 * * *" },
{ "path": "/api/cron/generate-tasks", "schedule": "0 22 * * *" }
]
}広告データ同期(20:00 UTC)の完了後にタスク生成(22:00 UTC)が実行される順序。
generate-daily-tasks.ts
がアクティブなルールを持つ全商材を取得evaluateRulesForProduct() を呼び出しtaskRules テーブルの各ルールに対して条件を評価dailyTasks
テーブルにタスクを生成evaluate-rules.ts)評価対象の指標:
| 指標 | 算出方法 | ルール例 |
|---|---|---|
cpa |
直近 7 日の cost / conversions | gt 目標 CPA 超過 |
ctr_change |
直近 7 日 vs 前 7 日の CTR 変化率 | change_percent 20%以上変動 |
clicks |
直近 7 日の合計クリック数 | lt クリック激減 |
frequency |
Meta 広告のフリクエンシー加重平均 | gt フリクエンシー過多 |
quality_score |
Google Ads キーワード品質スコア平均 | lt 品質低下 |
cpm_change |
直近 7 日 vs 前 7 日の CPM 変化率 | change_percent CPM 高騰 |
ルール演算子: gt(超過), lt(未満),
change_percent(変化率),
periodic_days(定期実行)
重複防止: 同日・同商材・同メトリクスのタスクはスキップ。
products)targetCpa: integer('target_cpa'), // 目標 CPA(円)
targetBudget: integer('target_budget'), // 目標予算(円)
targetConversions: integer('target_conversions'), // 目標 CV 数
alertThresholdPercent: integer('alert_threshold_percent').default(20), // アラート閾値(%)現行システムは データ母数を考慮せず 閾値超過のみで判定している。日次クリック数が 5 件の商材でも、CTR が 20% 変動したらアラートを出してしまう。これは統計的に無意味なノイズである。
月額予算 100 万円の商材でも、CPC が高ければ日次クリック数は少ない。逆に月額 30 万円でも CPC が低ければクリック数は多い。タスク生成頻度を決めるべきは予算額ではなく、実際に蓄積されるデータの量である。
日次クリック数に応じて、タスク生成頻度を 3 段階に分類する。
| 日次クリック数 | 頻度 | 根拠 |
|---|---|---|
| 100 clicks/日 超 | 毎日 | CTR・CPC の日次変動が統計的に有意。異常検知が即座に意味を持つ |
| 20-100 clicks/日 | 2-3 日ごと | 1 日のデータだけでは偶然変動と区別できない。2-3 日分を蓄積すれば傾向が見える |
| 20 clicks/日 未満 | 週次 | 日次・数日単位では標本数が不足。週単位でないとトレンド判定が不安定 |
CPA の変動を検知する場合、CV 数の母数が特に重要になる。
| 期間 CV 数 | CPA 判断の信頼度 | 推奨アクション |
|---|---|---|
| 50 CV 以上 | 高い。日次の CPA 変動にも意味がある | 日次 CPA アラート有効 |
| 30-50 CV | 中程度。3-7 日単位なら傾向が見える | 閾値を緩めて(例: 20% → 40% 変動)判定 |
| 10-30 CV | 低い。週単位で初めて傾向が分かる | 週次レポートでのみ CPA を評価 |
| 10 CV 未満 | 非常に低い。偶然変動が支配的 | CPA タスクを生成しない。クリック・インプレッションベースの指標に限定 |
| タスクカテゴリ | スキップ条件 | 理由 |
|---|---|---|
tcpa_adjustment |
週次 CV < 20 | CPA の信頼区間が広すぎて tCPA 調整の根拠にならない |
targeting (CTR ベース) |
日次 clicks < 20 | CTR の変動が標本誤差に埋もれる |
budget (CPA ベース) |
月次 CV < 30 | CPA が安定しないため予算配分の判断ができない |
adset_split |
週次 CV < 10(広告セット単位) | 分割テストの有意差が出ない |
frequency |
週次 reach < 1,000 | フリクエンシーの加重平均が不安定 |
cpm_change |
週次 impressions < 1,000 | CPM の変化率が標本誤差に埋もれる |
以下は低データ量でも生成を維持する。
| タスクカテゴリ | 理由 |
|---|---|
creative(定期更新) |
periodic_days ルールはデータ量に依存しない |
budget(消化率ベース) |
予算消化率は CV 数に依存せず、cost と budget の比較で判断可能 |
knowledge_suggestion |
ナレッジ提案はデータ量に関係なく価値がある |
| Quality Score 低下アラート | Google Ads の品質スコアは独自の指標で、クリック数の多寡に依存しない |
minDataVolume 条件を追加RuleDefinition インタフェースにオプショナルな
minDataVolume を追加する。
// apps/ops/src/lib/db/schema.ts - RuleDefinition の拡張
export interface RuleDefinition {
metric: string;
operator: RuleOperator;
threshold: number;
category: TaskCategory;
taskTemplate: string;
description: string;
// 新規追加
minDataVolume?: {
metric: 'clicks' | 'conversions' | 'impressions';
period: 'daily' | 'weekly'; // daily = recent7 の日平均, weekly = recent7 の合計
minValue: number;
};
}taskRules テーブルへのカラム追加:
// taskRules テーブルに追加
minDataMetric: text('min_data_metric'), // 'clicks' | 'conversions' | 'impressions'
minDataPeriod: text('min_data_period'), // 'daily' | 'weekly'
minDataValue: integer('min_data_value'), // 最小値evaluateRulesForProduct() にデータ量チェックを追加ルール評価の冒頭で、現在の metricValues
からデータ量を確認し、minDataVolume
条件を満たさないルールをスキップする。
// evaluate-rules.ts のルールループ内に追加するロジック
for (const rule of rules) {
// ... 既存の重複チェック ...
// データ量チェック(新規追加)
if (rule.minDataMetric && rule.minDataValue) {
const volumeKey = rule.minDataMetric as keyof MetricValues;
const currentVolume = metricValues[volumeKey];
// period が 'daily' の場合は 7 日平均、'weekly' の場合はそのまま
const effectiveVolume = rule.minDataPeriod === 'daily'
? (currentVolume ?? 0) / 7
: (currentVolume ?? 0);
if (effectiveVolume < rule.minDataValue) {
// データ量不足のためスキップ(ログ出力で可視化)
continue;
}
}
// ... 既存の条件評価 ...
}taskRuleTemplates のシステムテンプレートに、推奨される
minDataVolume をプリセットする。
// Google Ads 獲得型テンプレートの例
const googleAdsAcquisitionRules: RuleDefinition[] = [
{
metric: 'cpa',
operator: 'gt',
threshold: 120,
category: 'tcpa_adjustment',
taskTemplate: 'CPA が目標の {threshold}% を超過しています',
description: 'CPA 高騰アラート',
minDataVolume: {
metric: 'conversions',
period: 'weekly',
minValue: 20,
},
},
{
metric: 'ctr_change',
operator: 'change_percent',
threshold: 20,
category: 'targeting',
taskTemplate: 'CTR が {threshold}% 以上変動しています',
description: 'CTR 変動アラート',
minDataVolume: {
metric: 'clicks',
period: 'daily',
minValue: 50,
},
},
];minDataVolume
はオプショナルフィールドとし、未設定の場合は従来通り無条件で評価するdaily は
recent7 の日平均、weekly は
recent7 の合計値を使う。computeMetrics()
関数が既に 7 日分のデータを集計しているため、追加の DB クエリは不要minDataVolume
を適用し、効果を確認してから他の指標に展開する| ファイル | 役割 |
|---|---|
apps/ops/src/lib/tasks/evaluate-rules.ts |
ルール評価エンジン。MetricValues 計算、条件評価、タスク生成 |
apps/ops/src/lib/tasks/generate-daily-tasks.ts |
日次バッチ。アクティブルールを持つ商材を列挙して評価 |
apps/ops/src/lib/db/schema.ts |
products テーブル(targetCpa, targetBudget, alertThresholdPercent)、taskRules テーブル(metric, operator, threshold, ruleType) |
apps/ops/vercel.json |
Vercel Cron 定義(sync-ads: 20:00 UTC、generate-tasks: 22:00 UTC) |
apps/ops/src/app/api/cron/generate-tasks/route.ts |
Cron エンドポイント(認証付き GET) |