# タスク生成頻度の設計方針

## 背景

現在のタスク自動生成システムは、全商材に対して一律に毎日タスクを生成している。しかし広告運用の現場では、データ量が少ない商材に対して毎日アラートを出しても統計的に意味のある判断ができない。予算の大小ではなく、**データボリューム（クリック数・インプレッション数）の統計的有意性**に基づいてタスク生成頻度を制御すべきである。

---

## 現行システム

### 実行スケジュール

Vercel Cron で毎日 22:00 UTC（翌日 JST 7:00）に実行される。

```json
// 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）が実行される順序。

### タスク生成フロー

1. `generate-daily-tasks.ts` がアクティブなルールを持つ全商材を取得
2. 商材ごとに `evaluateRulesForProduct()` を呼び出し
3. 直近 14 日間のトレンドデータから指標を計算（直近 7 日 vs 前 7 日）
4. `taskRules` テーブルの各ルールに対して条件を評価
5. 条件を満たした場合、`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`)

```typescript
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/日 未満** | 週次 | 日次・数日単位では標本数が不足。週単位でないとトレンド判定が不安定 |

### CV（コンバージョン）の統計的有意性

CPA の変動を検知する場合、CV 数の母数が特に重要になる。

- **最低 30-50 CV** が蓄積されるまで、CPA の変動は統計的に有意と言えない
- 日次 CV が 1-2 件の商材では、1 件の CV 有無で CPA が 2 倍に振れる
- 週次 CV が 10 件未満の商材では、CPA アラートはノイズにしかならない

| 期間 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 の品質スコアは独自の指標で、クリック数の多寡に依存しない |

---

## 将来の実装計画

### Phase 1: ルール定義に `minDataVolume` 条件を追加

`RuleDefinition` インタフェースにオプショナルな `minDataVolume` を追加する。

```typescript
// 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` テーブルへのカラム追加:

```typescript
// taskRules テーブルに追加
minDataMetric: text('min_data_metric'),   // 'clicks' | 'conversions' | 'impressions'
minDataPeriod: text('min_data_period'),   // 'daily' | 'weekly'
minDataValue: integer('min_data_value'),  // 最小値
```

### Phase 2: `evaluateRulesForProduct()` にデータ量チェックを追加

ルール評価の冒頭で、現在の `metricValues` からデータ量を確認し、`minDataVolume` 条件を満たさないルールをスキップする。

```typescript
// 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;
    }
  }

  // ... 既存の条件評価 ...
}
```

### Phase 3: テンプレートにデフォルト値を設定

`taskRuleTemplates` のシステムテンプレートに、推奨される `minDataVolume` をプリセットする。

```typescript
// 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,
    },
  },
];
```

### 実装上の注意点

1. **既存ルールとの互換性**: `minDataVolume` はオプショナルフィールドとし、未設定の場合は従来通り無条件で評価する
2. **period の計算**: `daily` は `recent7` の日平均、`weekly` は `recent7` の合計値を使う。`computeMetrics()` 関数が既に 7 日分のデータを集計しているため、追加の DB クエリは不要
3. **ログ出力**: スキップされたルールはログに記録し、「なぜタスクが生成されなかったか」を運用者が確認できるようにする
4. **段階的な導入**: まず CPA 系ルールにのみ `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） |
