# 帳票設計書 14-トップコンテンツレポート

## 概要

本ドキュメントは、Ghost CMSにおけるトップコンテンツレポートの設計仕様を定義する。このレポートは、サイト内で最も閲覧されたページ/投稿の統計情報を、Tinybird分析基盤と連携してJSON形式でAPIエンドポイント経由で提供する。

### 本帳票の処理概要

本帳票は、サイト訪問者のページビュー（閲覧数）を分析し、最も人気のあるコンテンツをランキング形式で提供する。Tinybirdのリアルタイム分析エンジンを活用して、高速かつスケーラブルな集計を実現する。

**業務上の目的・背景**：コンテンツパフォーマンスの把握は、パブリッシャーにとって重要な指標である。どのページが最も閲覧されているかを知ることで、読者の興味・関心を理解し、今後のコンテンツ戦略の策定や、既存コンテンツの改善に活用できる。本レポートは、日付範囲、会員ステータス、デバイス、地域、UTMパラメータなど多様なフィルタオプションを提供し、詳細な分析を可能にする。

**帳票の利用シーン**：管理画面のStats（統計）セクションで「Top Content」タブを表示する際に利用される。指定した期間内で最も閲覧されたページ一覧を確認することで、コンテンツの人気度を把握できる。

**主要な出力内容**：
1. ページパス（pathname）と閲覧数（visits）
2. 投稿/ページ情報（post_uuid, post_id, title, post_type）
3. URLの有効性（url_exists）

**帳票の出力タイミング**：管理画面でトップコンテンツAPIエンドポイント（`GET /ghost/api/admin/stats/top-content`）にアクセスした際にリアルタイムで生成される。

**帳票の利用者**：サイト管理者、コンテンツマーケター、グロースハッカー

## 帳票種別

統計レポート / ランキング表（JSON形式）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Stats画面 | `/ghost/#/stats/` | Top Contentタブ選択時に自動取得 |
| - | Top Content API | `GET /ghost/api/admin/stats/top-content` | APIリクエスト |

## 出力形式

### 基本仕様

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

### レスポンス構造

```json
{
  "data": [
    {
      "pathname": "string",
      "visits": "number",
      "post_uuid": "string|null",
      "post_id": "string|null",
      "title": "string",
      "post_type": "string|null",
      "url_exists": "boolean"
    }
  ]
}
```

## 帳票レイアウト

### レイアウト概要

JSON配列形式でページビューランキングを返却する。閲覧数の多い順にソートされた配列として提供される。

```
{
  "data": [
    ┌─────────────────────────────────────┐
    │  pathname: /my-first-post           │
    │  visits: 1500                       │
    │  post_uuid: abc-123                 │
    │  post_id: 5f...                     │
    │  title: My First Post               │
    │  post_type: post                    │
    │  url_exists: true                   │
    └─────────────────────────────────────┘
    ...（複数レコード）
  ]
}
```

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 備考 |
|----|-------|------|-------------|---------|------|
| 1 | pathname | ページパス | Tinybird（api_top_pages） | 文字列 | 例: /my-post, / |
| 2 | visits | 閲覧数（ユニーク訪問者数） | Tinybird（api_top_pages） | 整数 | |
| 3 | post_uuid | 投稿UUID | Tinybird（api_top_pages） | 文字列 or null | |
| 4 | post_id | 投稿ID | posts.id（DBルックアップ） | 文字列 or null | post_uuidから逆引き |
| 5 | title | タイトル | posts.title or UrlService | 文字列 | パスから生成される場合もあり |
| 6 | post_type | コンテンツタイプ | posts.type | 'post', 'page', or null | |
| 7 | url_exists | URLの有効性 | UrlService | boolean | 現在存在するURLかどうか |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| date_from | 開始日（YYYY-MM-DD形式） | No |
| date_to | 終了日（YYYY-MM-DD形式） | No |
| timezone | タイムゾーン（デフォルト: UTC） | No |
| member_status | 会員ステータスフィルタ（'all', 'free', 'paid'等） | No |
| post_type | コンテンツタイプフィルタ（'post', 'page'） | No |
| post_uuid | 特定の投稿UUIDでフィルタ | No |
| pathname | 特定のパスでフィルタ | No |
| device | デバイスタイプフィルタ | No |
| location | 地域/国コードフィルタ | No |
| source | 参照元フィルタ | No |
| utm_source | UTMソースフィルタ | No |
| utm_medium | UTMメディアフィルタ | No |
| utm_campaign | UTMキャンペーンフィルタ | No |
| utm_content | UTMコンテンツフィルタ | No |
| utm_term | UTMタームフィルタ | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | visits | 降順（Tinybirdで事前ソート） |

### 改ページ条件

N/A（JSON形式のため改ページなし）

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| posts | 投稿タイトル・ID取得 | posts.uuid = Tinybird.post_uuid |
| Tinybird api_top_pages | ページビュー統計 | 主データソース |

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

#### posts

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| id | post_id | uuid IN (post_uuids from Tinybird) | |
| uuid | 結合キー | | |
| title | title | | |
| type | post_type | | 'post' or 'page' |

#### Tinybird api_top_pages

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| pathname | pathname | 各種フィルタ適用 | |
| visits | visits | | ユニーク訪問者数 |
| post_uuid | post_uuid | | 投稿に紐づくUUID |

## 計算仕様

### 計算項目一覧

本レポートには計算項目はない。全ての集計はTinybirdで実行される。

### タイトル生成ロジック

1. post_uuidが存在し、DBにマッチする場合: posts.titleを使用
2. UrlServiceで解決できる場合: resource.data.title または resource.data.name
3. 上記以外: pathnameから生成（先頭/末尾のスラッシュを除去、'/'の場合は'Homepage'）

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[APIリクエスト受信] --> B{Tinybird連携}
    B -->|無効| C[空配列返却]
    B -->|有効| D[パラメータをTinybird形式に変換]
    D --> E[Tinybird api_top_pages呼び出し]
    E --> F{データ取得成功}
    F -->|失敗| G[空配列返却]
    F -->|成功| H[post_uuid抽出]
    H --> I[DBからタイトル検索]
    I --> J[UrlServiceでタイトル補完]
    J --> K[URL存在チェック]
    K --> L[post_typeフィルタ適用]
    L --> M[結果をJSON形式で返却]
    M --> N[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| Tinybird未連携 | tinybirdClientがnull | N/A | 空配列返却 |
| Tinybirdエラー | API呼び出し失敗 | ログ出力 | 空配列返却 |
| データなし | 該当データなし | N/A | 空配列返却 |
| UrlServiceエラー | リソース検索失敗 | ログ出力（warn） | タイトルをpathnameから生成 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 通常100件程度（Tinybird側でlimit） |
| 目標出力時間 | 1-2秒以内（Tinybird依存） |
| 同時出力数上限 | 制限なし（通常のAPI制限に従う） |

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

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

## 備考

- Tinybird連携が必須。未連携の場合は空配列が返却される
- UrlServiceはGhostのルーティング情報を基にリソースを解決する
- post_typeフィルタは、Tinybirdからのデータ取得後にサーバーサイドで適用される
- url_existsは現在のサイト構成でURLが有効かどうかを示す（削除された投稿等を検出）

---

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

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

### 推奨読解順序

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

まず、トップコンテンツレポートで扱うデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `ghost/core/core/server/data/schema/schema.js` | postsテーブルのuuid, title, typeカラム（行61-105） |

**読解のコツ**: Tinybirdからのデータ（pathname, visits, post_uuid）とGhostデータベース（posts）を結合する構造を理解する。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | routes.js | `ghost/core/core/server/web/api/endpoints/admin/routes.js` | topContentのルート定義（行165） |
| 2-2 | stats.js | `ghost/core/core/server/api/endpoints/stats.js` | topContentコントローラー（行94-129） |

**主要処理フロー**:
1. **行165**: `GET /stats/top-content`ルート定義
2. **行94-129**: `topContent`コントローラー、多数のフィルタオプションを受け取りサービスを呼び出し

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | stats-service.js | `ghost/core/core/server/services/stats/stats-service.js` | getTopContentメソッド（行65-67） |
| 3-2 | content-stats-service.js | `ghost/core/core/server/services/stats/content-stats-service.js` | getTopContent実装（行45-68） |

**主要処理フロー**:
- **content-stats-service.js 行48-49**: Tinybirdクライアントの確認
- **content-stats-service.js 行53**: Tinybirdからの生データ取得
- **content-stats-service.js 行60**: タイトル等のエンリッチメント処理

#### Step 4: Tinybirdデータ取得を理解する

Tinybirdへのリクエスト構築を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | content-stats-service.js | `ghost/core/core/server/services/stats/content-stats-service.js` | fetchRawTopContentDataメソッド（行75-128） |

**主要処理フロー**:
- **行77-83**: snake_caseからcamelCaseへの変換
- **行86-125**: 各フィルタオプションの追加
- **行127**: Tinybird api_top_pagesエンドポイント呼び出し

#### Step 5: データエンリッチメントを理解する

タイトル検索とURL存在チェックのロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | content-stats-service.js | `ghost/core/core/server/services/stats/content-stats-service.js` | enrichTopContentData, lookupPostTitles, getResourceTitleメソッド（行134-293） |

**主要処理フロー**:
- **行150-173**: lookupPostTitles - DBからタイトル取得
- **行180-210**: getResourceTitle - UrlServiceでリソース解決
- **行217-293**: enrichTopContentData - データのエンリッチメント処理

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

```
routes.js (ルート定義)
    │
    └─ stats.js (APIコントローラー)
           │
           └─ topContent
                  └─ statsService.api.getTopContent(options)
                          └─ StatsService.getTopContent()
                                  └─ ContentStatsService.getTopContent()
                                          │
                                          ├─ fetchRawTopContentData()
                                          │      └─ Tinybird api_top_pages
                                          │
                                          └─ enrichTopContentData()
                                                 │
                                                 ├─ extractPostUuids()
                                                 │
                                                 ├─ lookupPostTitles()
                                                 │      └─ posts (DB)
                                                 │
                                                 ├─ getResourceTitle()
                                                 │      └─ UrlService
                                                 │
                                                 └─ URL存在チェック
                                                        └─ UrlService
```

### データフロー図

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

HTTPリクエスト ───▶ API Controller ───▶ ContentStatsService ───▶ JSON Response
  (date_from,          (パラメータ変換)                           { data: [...] }
   date_to,
   member_status,
   post_type, etc.)
                              │
                              ▼
                    ┌──────────────────┐
                    │   Tinybird       │
                    │   api_top_pages  │ ──▶ pathname, visits, post_uuid
                    └──────────────────┘
                              │
                              ▼
                    ┌──────────────────┐
                    │   Database       │
                    │   posts          │ ──▶ id, title, type
                    └──────────────────┘
                              │
                              ▼
                    ┌──────────────────┐
                    │   UrlService     │ ──▶ タイトル補完、URL存在チェック
                    └──────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| 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` | ソース | サービス層ラッパー |
| content-stats-service.js | `ghost/core/core/server/services/stats/content-stats-service.js` | ソース | トップコンテンツクエリ実装 |
| tinybird.js | `ghost/core/core/server/services/stats/utils/tinybird.js` | ソース | Tinybirdクライアント |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | 設定 | DBスキーマ定義 |
