# 帳票設計書 7-会員数履歴レポート

## 概要

会員数の履歴データを提供する統計レポートの設計書。Ghost CMSの会員管理機能において、無料会員、有料会員、コンプ（complimentary）会員の日次推移を集計して提供するAPIレスポンスを定義する。

### 本帳票の処理概要

本帳票は、Ghost CMSに登録されている会員数の日次推移データをJSON形式で提供する。会員ステータス（free/paid/comped）別の人数と、有料会員の購読開始・キャンセル数を日次で集計して返却する。

**業務上の目的・背景**：サイト運営者が会員数の成長推移を把握し、マーケティング施策の効果測定やトレンド分析を行うために利用される。無料会員から有料会員への転換率分析など、会員獲得戦略の策定に不可欠なデータとなる。

**帳票の利用シーン**：管理画面のDashboardやStats画面で会員数グラフを表示する際にAPIから取得される。外部BIツールとの連携やレポート作成にも活用できる。

**主要な出力内容**：
1. 日別会員数（free、paid、comped別）
2. 有料会員の購読開始数（paid_subscribed）
3. 有料会員のキャンセル数（paid_canceled）
4. 現在の会員数合計（ステータス別）

**帳票の出力タイミング**：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/member_count |
| 認証 | authAdminApi必須 |

## 帳票レイアウト

### レスポンス構造

```json
{
  "data": [
    {
      "date": "YYYY-MM-DD",
      "paid": 100,
      "free": 500,
      "comped": 10,
      "paid_subscribed": 5,
      "paid_canceled": 2
    }
  ],
  "meta": {
    "totals": {
      "paid": 100,
      "free": 500,
      "comped": 10
    }
  }
}
```

### 出力項目詳細

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | date | 日付 | 計算結果 | YYYY-MM-DD |
| 2 | paid | 有料会員数 | 計算結果 | 整数 |
| 3 | free | 無料会員数 | 計算結果 | 整数 |
| 4 | comped | コンプ会員数 | 計算結果 | 整数 |
| 5 | paid_subscribed | 有料購読開始数 | members_status_events | 整数 |
| 6 | paid_canceled | 有料キャンセル数 | members_status_events | 整数 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| dateFrom | 開始日（指定時は完全日付範囲を返却） | No |
| endDate | 終了日（デフォルト: 今日） | No |

### ソート順

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

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| members | 現在の会員数 | status集計 |
| members_status_events | ステータス変更履歴 | to_status/from_status集計 |

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

#### members

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| status | paid/free/comped | GROUP BY | 現在値 |
| id | COUNT | - | 会員数カウント |

#### members_status_events

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| to_status | paid_subscribed等 | CASE WHEN | 変更先ステータス |
| from_status | paid_canceled等 | CASE WHEN | 変更元ステータス |
| created_at | date | DATE() | 日付変換 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| paid_subscribed | SUM(CASE WHEN to_status='paid' THEN 1 ELSE 0 END) | - | 日次 |
| paid_canceled | SUM(CASE WHEN from_status='paid' THEN 1 ELSE 0 END) | - | 日次 |
| comped_delta | SUM(CASE WHEN to_status='comped' THEN 1 WHEN from_status='comped' THEN -1 ELSE 0 END) | - | 日次 |
| free_delta | SUM(CASE WHEN to_status='free' THEN 1 WHEN from_status='free' THEN -1 ELSE 0 END) | - | 日次 |

### 計算ロジック

1. 現在の会員数をステータス別に取得
2. 過去91日間（または指定期間）のステータス変更イベントを日次で取得
3. 現在値から逆算して各日の会員数を計算
4. startDate指定時は日付を埋めて完全な時系列を返却（forward-fill）

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[API要求] --> B[現在会員数取得]
    B --> C[デルタ履歴取得]
    C --> D{startDate指定?}
    D -->|Yes| E[完全日付範囲生成]
    D -->|No| F[スパース範囲生成]
    E --> G[逆算で会員数計算]
    F --> G
    G --> H[JSONレスポンス]
```

## エラー処理

### エラーケース一覧

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

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 〜91日 |
| 目標出力時間 | 1秒以内 |
| 同時出力数上限 | 特に制限なし |

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

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

## 備考

- startDate指定時はforward-fillで欠損日を埋める
- 会員数は0未満にならないようMath.max(0, x)で保護
- 未来日のデータはスキップ

---

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

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

### 推奨読解順序

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

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

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

**読解のコツ**: MemberStatusDelta、TotalMembersByStatus、CountHistoryの型定義で戻り値構造を把握する。

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

会員数履歴の計算ロジックを確認する。

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

**主要処理フロー**:
- **15-31行目**: getCount（現在の会員数取得）
- **39-68行目**: fetchAllStatusDeltas（変更履歴取得）
- **77-93行目**: getCountHistory（履歴計算分岐）
- **103-198行目**: _generateCompleteRange（完全日付範囲）
- **207-261行目**: _generateSparseRange（スパース範囲）

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

```
GET /ghost/api/admin/stats/member_count
    │
    ├─ api/endpoints/stats.js#memberCountHistory
    │      │
    │      └─ StatsService.getMemberCountHistory(options)
    │             │
    │             └─ MembersStatsService.getCountHistory(options)
    │                    │
    │                    ├─ getCount()
    │                    │      └─ SELECT status, COUNT(id)
    │                    │         FROM members GROUP BY status
    │                    │
    │                    ├─ fetchAllStatusDeltas(options)
    │                    │      └─ SELECT DATE(created_at), SUM(CASE...)
    │                    │         FROM members_status_events
    │                    │
    │                    └─ [startDate指定?]
    │                           ├─ _generateCompleteRange() [Yes]
    │                           └─ _generateSparseRange() [No]
```

### データフロー図

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

APIリクエスト ───▶ StatsService ───▶ MembersStatsService
(dateFrom?)                              │
                                         ▼
                              ┌─────────────────┐
                              │ 現在会員数取得   │
                              │ (ステータス別)   │
                              └─────────────────┘
                                         │
                                         ▼
                              ┌─────────────────┐
                              │ デルタ履歴取得  │
                              │ (91日 or指定)   │
                              └─────────────────┘
                                         │
                                         ▼
                              ┌─────────────────┐
                              │ 逆算で会員数計算 │
                              │ (現在→過去)     │
                              └─────────────────┘
                                         │
                                         ▼
                              ┌─────────────────┐
                              │ JSONレスポンス  │
                              │ {data, meta}    │
                              └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| members-stats-service.js | `ghost/core/core/server/services/stats/members-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` | ソース | ルーティング定義 |
