# 帳票設計書 11-ニュースレター統計レポート

## 概要

本ドキュメントは、Ghost CMSにおけるニュースレター統計レポートの設計仕様を定義する。このレポートは、ニュースレター別の送信統計（送信数、開封数、開封率、クリック数、クリック率）を投稿ごとに集計し、JSON形式でAPIエンドポイント経由で提供する。

### 本帳票の処理概要

本帳票は、Ghost CMSで配信されたニュースレターのパフォーマンス指標を可視化し、コンテンツマーケティングの効果測定を支援するための統計データを提供する。

**業務上の目的・背景**：ニュースレターは現代のデジタルパブリッシングにおいて重要な読者エンゲージメント手段である。サイト運営者は配信したニュースレターの効果を定量的に把握し、開封率やクリック率の傾向を分析することで、コンテンツ戦略の改善やより効果的な配信タイミングの決定を行う必要がある。本レポートはこれらの分析に必要なデータを一元的に提供する。

**帳票の利用シーン**：管理画面のStats（統計）セクションでニュースレターのパフォーマンスを確認する際に利用される。特定のニュースレターを選択し、配信済み投稿の送信・開封・クリック統計を時系列で確認することで、読者の反応パターンを把握できる。

**主要な出力内容**：
1. 投稿ID（post_id）と投稿タイトル（post_title）
2. 送信日時（send_date）と送信数（sent_to）
3. 開封数（total_opens）と開封率（open_rate）
4. クリック数（total_clicks）とクリック率（click_rate）

**帳票の出力タイミング**：管理画面でニュースレター統計APIエンドポイント（`GET /ghost/api/admin/stats/newsletter-stats`）にアクセスした際にリアルタイムで生成される。

**帳票の利用者**：サイト管理者、コンテンツマーケター、編集者

## 帳票種別

統計レポート / 集計表（JSON形式）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Stats画面 | `/ghost/#/stats/` | ニュースレター選択時に自動取得 |
| - | Newsletter Stats API | `GET /ghost/api/admin/stats/newsletter-stats` | APIリクエスト |
| - | Newsletter Basic Stats API | `GET /ghost/api/admin/stats/newsletter-basic-stats` | APIリクエスト |
| - | Newsletter Click Stats API | `GET /ghost/api/admin/stats/newsletter-click-stats` | APIリクエスト |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | JSON |
| 用紙サイズ | N/A（API出力） |
| 向き | N/A |
| ファイル名 | N/A（HTTPレスポンス） |
| 出力方法 | API経由でJSONレスポンス |
| 文字コード | UTF-8 |

### レスポンス構造

```json
{
  "data": [
    {
      "post_id": "string",
      "post_title": "string",
      "send_date": "datetime",
      "sent_to": "number",
      "total_opens": "number",
      "open_rate": "number",
      "total_clicks": "number",
      "click_rate": "number"
    }
  ]
}
```

## 帳票レイアウト

### レイアウト概要

JSON配列形式で投稿ごとのニュースレター統計を返却する。

```
{
  "data": [
    ┌─────────────────────────────────────┐
    │  投稿情報（post_id, post_title）     │
    │  送信情報（send_date, sent_to）      │
    │  開封情報（total_opens, open_rate）  │
    │  クリック情報（total_clicks,         │
    │              click_rate）           │
    └─────────────────────────────────────┘
    ...（複数レコード）
  ]
}
```

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 備考 |
|----|-------|------|-------------|---------|------|
| 1 | post_id | 投稿ID | posts.id | 文字列（24文字） | 主キー |
| 2 | post_title | 投稿タイトル | posts.title | 文字列 | |
| 3 | send_date | 送信日時 | posts.published_at | ISO8601形式 | 公開日時を送信日時として使用 |
| 4 | sent_to | 送信数 | emails.email_count | 整数 | COALESCE(0) |
| 5 | total_opens | 開封数 | emails.opened_count | 整数 | COALESCE(0) |
| 6 | open_rate | 開封率 | 計算値 | 小数（0-1） | opened_count / email_count |
| 7 | total_clicks | クリック数 | members_click_events経由 | 整数 | ユニーク会員数 |
| 8 | click_rate | クリック率 | 計算値 | 小数（0-1） | click_count / email_count |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| newsletter_id | 特定のニュースレターIDでフィルタ | Yes |
| date_from | 開始日（YYYY-MM-DD形式） | No |
| date_to | 終了日（YYYY-MM-DD形式） | No |
| timezone | タイムゾーン（デフォルト: UTC） | No |
| order | ソート順（date, open_rate, click_rate） | No |
| limit | 取得件数上限（デフォルト: 20） | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | send_date（デフォルト） | 降順 |
| 代替 | open_rate | 降順 |
| 代替 | click_rate | 降順 |
| 代替 | sent_to | 降順 |

### 改ページ条件

N/A（JSON形式のため改ページなし。limit パラメータで件数を制限）

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| posts | 投稿情報の取得 | 主テーブル |
| emails | メール送信統計 | emails.post_id = posts.id |
| redirects | クリックトラッキング用リダイレクト | redirects.post_id = posts.id |
| members_click_events | クリックイベント | members_click_events.redirect_id = redirects.id |

### テーブル別参照項目詳細

#### posts

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| id | post_id | newsletter_id = 指定値 | |
| title | post_title | status IN ('sent', 'published') | |
| published_at | send_date | | |
| newsletter_id | フィルタ条件 | | 外部キー |
| status | フィルタ条件 | 'sent' または 'published' | |

#### emails

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| email_count | sent_to | | COALESCE(0) |
| opened_count | total_opens | | COALESCE(0) |
| post_id | 結合キー | | |

#### redirects + members_click_events

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| COUNT(DISTINCT member_id) | total_clicks | redirects.post_id IS NOT NULL | サブクエリで集計 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| open_rate | CASE WHEN email_count > 0 THEN opened_count / email_count ELSE 0 END | なし（小数のまま） | 0-1の範囲 |
| click_rate | CASE WHEN email_count > 0 THEN click_count / email_count ELSE 0 END | なし（小数のまま） | 0-1の範囲 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[APIリクエスト受信] --> B{newsletter_id チェック}
    B -->|未指定| C[空配列返却]
    B -->|指定あり| D[パラメータ検証]
    D --> E[日付境界の計算]
    E --> F[クリック数サブクエリ構築]
    F --> G[メインクエリ実行]
    G --> H[結果をJSON形式で返却]
    H --> I[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| パラメータエラー | 不正なorder値 | Invalid order field: {field}. Must be one of: date, open_rate, click_rate | 正しいorder値を指定 |
| パラメータエラー | 不正なソート方向 | Invalid order direction: {direction} | asc または desc を指定 |
| データなし | newsletter_id未指定 | N/A（空配列返却） | newsletter_idを指定 |
| 内部エラー | クエリ実行失敗 | ログ出力のみ | 空配列返却、ログ確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千件（limit=20がデフォルト） |
| 目標出力時間 | 1秒以内 |
| 同時出力数上限 | 制限なし（通常のAPI制限に従う） |

## セキュリティ考慮事項

- Admin API認証が必須（mw.authAdminApi）
- members権限のbrowseパーミッションが必要
- APIキャッシュによる負荷軽減（statsService.cache）
- 個人を特定できる情報（メールアドレス等）は含まれない

## 備考

- Newsletter Stats APIは3つのエンドポイントに分割されている：
  1. `newsletter-stats`: 全統計（送信、開封、クリック）
  2. `newsletter-basic-stats`: 基本統計（送信、開封のみ、高速）
  3. `newsletter-click-stats`: クリック統計のみ（遅延ロード用）
- クリックデータの取得は負荷が高いため、BasicStatsで初期表示し、必要に応じてClickStatsを別途取得する設計

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、ニュースレター統計レポートで扱うデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `ghost/core/core/server/data/schema/schema.js` | posts, emails, newsletters, redirects, members_click_eventsテーブルの構造（行826-1018） |

**読解のコツ**: 特に`emails`テーブルの`email_count`, `opened_count`カラムと、`posts`テーブルの`newsletter_id`外部キーに注目する。

#### Step 2: エントリーポイントを理解する

処理の起点となるAPIルーティングとコントローラーを特定。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | routes.js | `ghost/core/core/server/web/api/endpoints/admin/routes.js` | ニュースレター統計関連のルート定義（行166-169） |
| 2-2 | stats.js | `ghost/core/core/server/api/endpoints/stats.js` | APIエンドポイントコントローラー（行196-289） |

**主要処理フロー**:
1. **行166-169**: `newsletter-stats`, `newsletter-basic-stats`, `newsletter-click-stats`のルート定義
2. **行196-228**: `newsletterStats`コントローラー（全統計取得）
3. **行230-262**: `newsletterBasicStats`コントローラー（基本統計取得）
4. **行264-289**: `newsletterClickStats`コントローラー（クリック統計取得）

#### Step 3: サービス層を理解する

ビジネスロジックの中核となるサービスクラスを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | stats-service.js | `ghost/core/core/server/services/stats/stats-service.js` | StatsServiceクラスのラッパーメソッド（行119-223） |
| 3-2 | posts-stats-service.js | `ghost/core/core/server/services/stats/posts-stats-service.js` | 実際のクエリ構築ロジック（行693-925） |

**主要処理フロー**:
- **stats-service.js 行119-131**: `getNewsletterStats`メソッド
- **stats-service.js 行143-154**: `getNewsletterSubscriberStats`メソッド
- **stats-service.js 行190-202**: `getNewsletterBasicStats`メソッド
- **stats-service.js 行211-223**: `getNewsletterClickStats`メソッド
- **posts-stats-service.js 行693-765**: `getNewsletterStats`実装（クリックサブクエリ含む）
- **posts-stats-service.js 行779-876**: `getNewsletterBasicStats`実装
- **posts-stats-service.js 行885-925**: `getNewsletterClickStats`実装

#### Step 4: ユーティリティを理解する

日付処理などの共通ユーティリティを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | date-utils.js | `ghost/core/core/server/services/stats/utils/date-utils.js` | 日付境界計算とフィルタ適用 |

### プログラム呼び出し階層図

```
routes.js (ルート定義)
    │
    └─ stats.js (APIコントローラー)
           │
           ├─ newsletterStats
           │      └─ statsService.api.getNewsletterStats()
           │              └─ StatsService.getNewsletterStats()
           │                      └─ PostsStatsService.getNewsletterStats()
           │
           ├─ newsletterBasicStats
           │      └─ statsService.api.getNewsletterBasicStats()
           │              └─ StatsService.getNewsletterBasicStats()
           │                      └─ PostsStatsService.getNewsletterBasicStats()
           │
           └─ newsletterClickStats
                  └─ statsService.api.getNewsletterClickStats()
                          └─ StatsService.getNewsletterClickStats()
                                  └─ PostsStatsService.getNewsletterClickStats()
```

### データフロー図

```
[入力]                    [処理]                           [出力]

HTTPリクエスト ───▶ API Controller ───▶ StatsService ───▶ JSON Response
  (newsletter_id,        (パラメータ検証)      (クエリ構築・実行)
   order, limit等)

                              │
                              ▼
                    ┌──────────────────┐
                    │   Database       │
                    │ ├─ posts         │
                    │ ├─ emails        │
                    │ ├─ redirects     │
                    │ └─ members_click │
                    │     _events      │
                    └──────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| routes.js | `ghost/core/core/server/web/api/endpoints/admin/routes.js` | ソース | APIルート定義 |
| stats.js | `ghost/core/core/server/api/endpoints/stats.js` | ソース | APIコントローラー |
| stats-service.js | `ghost/core/core/server/services/stats/stats-service.js` | ソース | サービス層ラッパー |
| posts-stats-service.js | `ghost/core/core/server/services/stats/posts-stats-service.js` | ソース | ニュースレター統計クエリ実装 |
| date-utils.js | `ghost/core/core/server/services/stats/utils/date-utils.js` | ソース | 日付ユーティリティ |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | 設定 | DBスキーマ定義 |
| service.js | `ghost/core/core/server/services/stats/service.js` | ソース | サービス初期化 |
| index.js | `ghost/core/core/server/services/stats/index.js` | ソース | サービスエクスポート |
