# 帳票設計書 8-購読履歴レポート

## 概要

有料購読（サブスクリプション）の履歴データを提供する統計レポートの設計書。Ghost CMSの有料会員機能において、ティア（商品）別・課金周期別の購読数推移を日次で集計して提供するAPIレスポンスを定義する。

### 本帳票の処理概要

本帳票は、Ghost CMSの有料サブスクリプション数の日次推移データをJSON形式で提供する。ティア（product）別、課金周期（月次/年次）別に購読の開始・キャンセル数を日次で集計し、累計購読数を計算して返却する。

**業務上の目的・背景**：サイト運営者が有料購読の成長推移を把握し、どのティア・課金周期が人気かを分析するために利用される。年間購読と月間購読の比率分析など、価格戦略の策定に不可欠なデータとなる。

**帳票の利用シーン**：管理画面のDashboardやStats画面で購読数グラフを表示する際にAPIから取得される。ティア別の購読動向分析や外部BIツールとの連携にも活用できる。

**主要な出力内容**：
1. 日別購読数（ティア別、課金周期別）
2. 日別の購読開始数（signups）
3. 日別のキャンセル数（cancellations）
4. 正味変動（positive_delta、negative_delta）
5. 現在の購読数合計（ティア別、課金周期別）

**帳票の出力タイミング**：APIエンドポイントへのリクエスト時にリアルタイムで生成される。

**帳票の利用者**：サイト管理者、マーケティング担当者。ダッシュボードの購読数グラフ表示や外部連携に使用。

## 帳票種別

統計レポート（JSON API）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Dashboard | /ghost/#/dashboard | 自動取得 |
| - | Stats | /ghost/#/stats | 自動取得 |

## 出力形式

### 基本仕様

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

### APIエンドポイント

| 項目 | 内容 |
|-----|------|
| HTTPメソッド | GET |
| パス | /ghost/api/admin/stats/subscriptions |
| 認証 | authAdminApi必須 |

## 帳票レイアウト

### レスポンス構造

```json
{
  "data": [
    {
      "date": "YYYY-MM-DD",
      "tier": "product-id-uuid",
      "cadence": "month",
      "positive_delta": 5,
      "negative_delta": 2,
      "signups": 5,
      "cancellations": 2,
      "count": 100
    }
  ],
  "meta": {
    "cadences": ["month", "year"],
    "tiers": ["product-id-1", "product-id-2"],
    "totals": [
      {
        "tier": "product-id-uuid",
        "cadence": "month",
        "count": 100
      }
    ]
  }
}
```

### 出力項目詳細

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | date | 日付 | members_paid_subscription_events.created_at | YYYY-MM-DD |
| 2 | tier | ティア（商品）ID | products.id | UUID |
| 3 | cadence | 課金周期 | stripe_prices.interval | month/year |
| 4 | positive_delta | 正の変動数 | 計算結果 | 整数 |
| 5 | negative_delta | 負の変動数 | 計算結果 | 整数 |
| 6 | signups | 購読開始数 | 計算結果 | 整数 |
| 7 | cancellations | キャンセル数 | 計算結果 | 整数 |
| 8 | count | その日時点の累計数 | 計算結果 | 整数 |

### メタデータ

| 項目名 | 説明 |
|-------|------|
| cadences | 存在する課金周期の配列 |
| tiers | 存在するティアIDの配列 |
| totals | 現在の購読数合計（ティア別・課金周期別） |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| - | 特にパラメータなし（全期間） | - |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | date | 昇順 |

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| members_paid_subscription_events | 購読イベント履歴 | 主テーブル |
| stripe_prices | 価格情報 | stripe_price_id結合 |
| stripe_products | Stripe商品情報 | stripe_product_id結合 |
| products | Ghost商品（ティア） | product_id結合 |
| members_stripe_customers_subscriptions | 現在の購読状態 | 現在値取得 |

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

#### members_paid_subscription_events

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| created_at | date | DATE() | 日付変換 |
| type | signups/cancellations | CASE WHEN | イベント種別判定 |
| from_plan | negative_delta計算 | JOIN | 変更元プラン |
| to_plan | positive_delta計算 | JOIN | 変更先プラン |
| mrr_delta | 変動判定 | != 0 | MRR変動あり |

#### stripe_prices

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| stripe_price_id | - | JOIN条件 | プラン識別 |
| interval | cadence | SELECT | month/year |
| stripe_product_id | - | JOIN条件 | 商品結合用 |

#### products

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| id | tier | SELECT | ティアID |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| positive_delta | SUM(CASE created/reactivated/active OR updated→to_plan) | - | 新規・再開・プラン変更先 |
| negative_delta | SUM(CASE canceled/expired/inactive OR updated→from_plan) | - | キャンセル・失効・プラン変更元 |
| signups | SUM(CASE created/reactivated/active, mrr_delta>0時のupdated) | - | 実質的な新規購読 |
| cancellations | SUM(CASE canceled/expired/inactive) | - | 実質的なキャンセル |
| count | 現在値から逆算 | - | 累計購読数 |

### 計算ロジック

1. 現在のティア別・課金周期別購読数を取得（fetchSubscriptionCounts）
2. 全期間の購読イベントを日次・ティア・課金周期別に集計（fetchAllSubscriptionDeltas）
3. 現在値から逆算して各日の累計購読数を計算
4. 結果を日付昇順でソート

### イベント種別判定詳細

**positive_delta（購読増加）の条件**:
- type = 'created', 'reactivated', 'active' かつ mrr_delta != 0
- type = 'updated' かつ price.id = to_price.id（プラン変更先）
- type = 'updated' かつ from_plan = to_plan かつ mrr_delta > 0

**negative_delta（購読減少）の条件**:
- type = 'canceled', 'expired', 'inactive' かつ mrr_delta != 0
- type = 'updated' かつ price.id = from_price.id（プラン変更元）

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[API要求] --> B[現在購読数取得]
    B --> C[デルタ履歴取得]
    C --> D[ティア・課金周期でグループ化]
    D --> E[逆算で累計数計算]
    E --> F[メタデータ生成]
    F --> G[JSONレスポンス]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 認証エラー | 認証なし | 401 Unauthorized | ログイン必要 |
| データなし | 有料購読なし | 空配列 | - |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 全期間のイベント数 |
| 目標出力時間 | 1秒以内 |
| 同時出力数上限 | 特に制限なし |

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

- 管理者認証が必要
- 購読統計のため個人情報は含まない

## 備考

- 逆算時にcount値が負にならないよう保護が必要
- ティアIDはGhost内部のproductsテーブルのID
- 課金周期はStripeのinterval値をそのまま使用

---

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

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

### 推奨読解順序

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

まず、購読数計算のデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | subscription-stats-service.js | `ghost/core/core/server/services/stats/subscription-stats-service.js` | 型定義（151-178行目） |

**読解のコツ**: SubscriptionDelta、SubscriptionCount、SubscriptionHistoryEntryの型定義で戻り値構造を把握する。

#### Step 2: 計算ロジックを理解する

購読履歴の計算ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | subscription-stats-service.js | `ghost/core/core/server/services/stats/subscription-stats-service.js` | getSubscriptionHistory関数（14-69行目） |

**主要処理フロー**:
- **14-69行目**: getSubscriptionHistory（メイン処理）
- **74-126行目**: fetchAllSubscriptionDeltas（デルタ取得クエリ）
- **132-148行目**: fetchSubscriptionCounts（現在値取得）

**CASE WHEN判定の詳細**（94-121行目）:
- positive_delta: 新規購読・再開・プラン変更先をカウント
- negative_delta: キャンセル・失効・プラン変更元をカウント
- signups: 実質的な新規購読数
- cancellations: 実質的なキャンセル数

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

```
GET /ghost/api/admin/stats/subscriptions
    │
    ├─ api/endpoints/stats.js#subscriptions
    │      │
    │      └─ StatsService.getSubscriptionHistory()
    │             │
    │             └─ SubscriptionStatsService.getSubscriptionHistory()
    │                    │
    │                    ├─ fetchAllSubscriptionDeltas()
    │                    │      └─ SELECT DATE(created_at), tier, cadence,
    │                    │         SUM(CASE WHEN type IN (...))
    │                    │         FROM members_paid_subscription_events
    │                    │         JOIN stripe_prices, stripe_products, products
    │                    │
    │                    └─ fetchSubscriptionCounts()
    │                           └─ SELECT COUNT(*), tier, cadence
    │                              FROM members_stripe_customers_subscriptions
    │                              JOIN stripe_prices, stripe_products, products
```

### データフロー図

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

APIリクエスト ───▶ StatsService ───▶ SubscriptionStatsService
                                            │
                                            ▼
                              ┌─────────────────────┐
                              │ 現在購読数取得      │
                              │ (ティア・課金周期別)│
                              └─────────────────────┘
                                            │
                                            ▼
                              ┌─────────────────────┐
                              │ デルタ履歴取得      │
                              │ (全期間)            │
                              └─────────────────────┘
                                            │
                                            ▼
                              ┌─────────────────────┐
                              │ 逆算で累計数計算    │
                              │ (現在→過去)        │
                              └─────────────────────┘
                                            │
                                            ▼
                              ┌─────────────────────┐
                              │ メタデータ生成      │
                              │ (cadences,tiers,    │
                              │  totals)            │
                              └─────────────────────┘
                                            │
                                            ▼
                              ┌─────────────────────┐
                              │ JSONレスポンス      │
                              │ {data, meta}        │
                              └─────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| subscription-stats-service.js | `ghost/core/core/server/services/stats/subscription-stats-service.js` | ソース | 購読数計算ロジック |
| stats-service.js | `ghost/core/core/server/services/stats/stats-service.js` | ソース | 統計サービス集約 |
| routes.js | `ghost/core/core/server/web/api/endpoints/admin/routes.js` | ソース | ルーティング定義 |
