# 帳票設計書 2-投稿分析CSV

## 概要

投稿（Posts）の分析データをCSV形式でエクスポートする帳票設計書。Ghost CMSの投稿管理機能において、公開済み・送信済みの投稿に関するパフォーマンス指標を一括でCSVファイルとして出力する機能を定義する。

### 本帳票の処理概要

本帳票は、Ghost CMSに登録されている投稿の分析データをCSV形式でエクスポートする機能を提供する。投稿の基本情報に加え、メール配信統計、会員獲得統計、フィードバック情報など、コンテンツマーケティングの効果測定に必要なデータを網羅的に出力する。

**業務上の目的・背景**：コンテンツマーケティング担当者が投稿ごとのパフォーマンスを分析し、どの投稿が会員獲得に貢献しているか、どのコンテンツが読者に好評かを把握するために利用される。外部のBIツールやスプレッドシートでの詳細分析にも活用できる。

**帳票の利用シーン**：管理画面のPostsセクションから「Export」ボタンをクリックして利用する。デフォルトでは公開済み（published）および送信済み（sent）の投稿が対象となる。フィルター条件を設定して特定の投稿のみをエクスポートすることも可能。

**主要な出力内容**：
1. 投稿基本情報（ID、タイトル、URL、著者、ステータス）
2. 日時情報（作成日時、更新日時、公開日時）
3. メタ情報（注目記事フラグ、タグ、アクセス権限）
4. メール配信統計（送信数、開封数、クリック数）
5. 会員獲得統計（サインアップ数、有料コンバージョン数）
6. フィードバック情報（More/Less評価）

**帳票の出力タイミング**：管理者がPostsセクションで「Export」操作を実行した時点でリアルタイムに生成される。フィルターや検索条件が設定されている場合は、その条件に合致する投稿のみがエクスポート対象となる。

**帳票の利用者**：サイト管理者、コンテンツマーケティング担当者、編集者。レポート作成やコンテンツ戦略の策定に活用される。

## 帳票種別

一覧表（CSVエクスポート）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | Posts一覧画面 | /ghost/#/posts | Exportボタン |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | CSV |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | post-analytics.{YYYY-MM-DD}.csv |
| 出力方法 | ダウンロード |
| 文字コード | UTF-8 |

### CSV固有設定

| 項目 | 内容 |
|-----|------|
| 区切り文字 | カンマ（,） |
| 引用符 | ダブルクォート（"） |
| ヘッダー行 | 有 |

## 帳票レイアウト

### レイアウト概要

CSVファイルは1行目がヘッダー行、2行目以降がデータ行の標準的なCSV形式。設定によりカラムが動的に増減する。

```
┌─────────────────────────────────────┐
│          ヘッダー行（カラム名）        │
├─────────────────────────────────────┤
│          データ行1                   │
│          データ行2                   │
│          ...                        │
│          データ行N                   │
└─────────────────────────────────────┘
```

### ヘッダー部（CSVカラム）

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 条件付き |
|----|-------|------|-------------|---------|---------|
| 1 | id | 投稿ID | posts.id | UUID形式 | - |
| 2 | title | タイトル | posts.title | 文字列 | - |
| 3 | url | 投稿URL | getPostUrl() | 絶対URL | - |
| 4 | author | 著者名 | posts_authors + users | カンマ区切り | - |
| 5 | status | ステータス | posts.status + email有無 | 変換後文字列 | - |
| 6 | created_at | 作成日時 | posts.created_at | ISO8601形式 | - |
| 7 | updated_at | 更新日時 | posts.updated_at | ISO8601形式 | - |
| 8 | published_at | 公開日時 | posts.published_at | ISO8601形式 | - |
| 9 | featured | 注目記事フラグ | posts.featured | true/false | - |
| 10 | tags | タグ | posts_tags + tags | カンマ区切り | - |
| 11 | post_access | アクセス権限 | posts.visibility | 変換後文字列 | - |
| 12 | email_recipients | メール受信者フィルター | emails.recipient_filter | 人間可読形式 | Members有効時のみ |
| 13 | newsletter_name | ニュースレター名 | newsletters.name | 文字列 | 複数Newsletter時のみ |
| 14 | sends | メール送信数 | emails.email_count | 数値 | Members有効時のみ |
| 15 | opens | 開封数 | emails.opened_count | 数値 | trackOpens有効時のみ |
| 16 | clicks | クリック数 | count__clicks | 数値 | trackClicks有効時のみ |
| 17 | signups | サインアップ数 | count__signups | 数値 | trackSources有効時のみ |
| 18 | paid_conversions | 有料コンバージョン数 | count__paid_conversions | 数値 | paidMembers有効時のみ |
| 19 | feedback_more_like_this | 好評フィードバック | count__positive_feedback | 数値 | feedback有効時のみ |
| 20 | feedback_less_like_this | 不評フィードバック | count__negative_feedback | 数値 | feedback有効時のみ |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| filter | NQL形式のフィルター条件（デフォルト: status:published,status:sent） | No |
| order | ソート順 | No |
| limit | 取得件数制限 | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | 指定なし（デフォルト） | - |

### 改ページ条件

N/A（CSVファイルのため改ページ不要）

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| posts | 投稿基本情報 | 主テーブル |
| posts_authors + users | 著者情報 | post_id = posts.id |
| posts_tags + tags | タグ情報 | post_id = posts.id |
| posts_products (tiers) | Tier情報 | post_id = posts.id |
| emails | メール配信情報 | post_id = posts.id |
| newsletters | ニュースレター情報 | id = posts.newsletter_id |
| labels | ラベル情報 | 受信者フィルター解析用 |
| products | 製品情報 | 受信者フィルター解析用 |

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

#### posts

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| id | id | - | UUID形式 |
| title | title | - | - |
| status | status | - | mapPostStatus()で変換 |
| created_at | created_at | - | ISO8601形式 |
| updated_at | updated_at | - | ISO8601形式 |
| published_at | published_at | draft/scheduled以外 | ISO8601形式 |
| featured | featured | - | 真偽値 |
| visibility | post_access | - | postAccessToString()で変換 |
| newsletter_id | newsletter_name参照用 | - | - |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| status | mapPostStatus(posts.status, !!email) | - | draft/scheduled/emailed only/published and emailed/published only |
| post_access | postAccessToString(visibility, tiers) | - | Public/Members-only/Paid members-only/Specific tiers: X |
| email_recipients | humanReadableEmailRecipientFilter(recipient_filter, labels, tiers) | - | NQLフィルターを人間可読形式に変換 |
| author | posts.authors.map(a => a.name).join(', ') | - | 複数著者をカンマ区切り |
| tags | posts.tags.map(t => t.name).join(', ') | - | 複数タグをカンマ区切り |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[エクスポート要求] --> B[パラメータ検証]
    B --> C[投稿データ取得]
    C --> D[関連データ取得]
    D --> E{設定確認}
    E --> F[カラム決定]
    F --> G[データマッピング]
    G --> H[不要カラム削除]
    H --> I[CSVレスポンス生成]
    I --> J[ファイル返却]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 認証エラー | 認証なしでアクセス | 401 Unauthorized | ログイン必要 |
| 権限エラー | browse権限がない | 403 Forbidden | 権限付与必要 |
| データなし | 該当投稿なし | 空のCSV | - |

## パフォーマンス要件

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

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

- 管理者認証が必要（authAdminApi）
- posts.browse権限の確認
- メール統計データを含むため、適切なアクセス制御が必要

## 備考

- 設定（Members有効/無効、trackOpens、trackClicks、trackSources等）により出力カラムが動的に変化する
- draft/scheduledステータスの投稿はメール関連データがクリアされる
- フィードバック列はニュースレターでフィードバックが有効な場合のみ出力

---

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

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

### 推奨読解順序

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

まず、エクスポートされるデータの構造と、関連するDBテーブルの関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | posts-exporter.js | `ghost/core/core/server/services/posts/posts-exporter.js` | エクスポートデータの構造、カラム決定ロジック |

**読解のコツ**: `export`メソッド（35-142行目）でデータ取得からマッピング、カラム削除までの全フローを確認する。

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

処理の起点となるAPIエンドポイントを特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | posts.js | `ghost/core/core/server/api/endpoints/posts.js` | exportCSVアクションの定義 |
| 2-2 | routes.js | `ghost/core/core/server/web/api/endpoints/admin/routes.js` | /posts/exportルートの定義（32行目） |

**主要処理フロー**:
1. **32行目**: GET /posts/exportルート定義
2. APIエンドポイントからpostsExporter.export()呼び出し

#### Step 3: エクスポーターの詳細を理解する

データ変換ロジックの詳細を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | posts-exporter.js | `ghost/core/core/server/services/posts/posts-exporter.js` | 全体的なエクスポートロジック |

**主要処理フロー**:
- **36-51行目**: 投稿データ取得（関連データ含む）
- **53-62行目**: 設定値の取得（Members有効/無効、trackOpens等）
- **64-104行目**: データマッピング（各カラムの値設定）
- **106-138行目**: 設定に応じた不要カラムの削除
- **144-164行目**: mapPostStatus()でステータス変換
- **166-190行目**: postAccessToString()でアクセス権限変換
- **199-276行目**: humanReadableEmailRecipientFilter()で受信者フィルター変換

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

```
GET /ghost/api/admin/posts/export
    │
    ├─ api/endpoints/posts.js#exportCSV
    │      │
    │      └─ services/posts/posts-exporter.js#export
    │             │
    │             ├─ models.Post.findPage()
    │             │      └─ withRelated: [tiers, tags, authors, count.*, email]
    │             │
    │             ├─ models.Newsletter.findAll()
    │             ├─ models.Label.findAll()
    │             └─ models.Product.findAll()
    │
    │             ├─ mapPostStatus()
    │             ├─ postAccessToString()
    │             └─ humanReadableEmailRecipientFilter()
```

### データフロー図

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

リクエスト ───▶ エンドポイント ───▶ PostsExporter
(filter/order)    (posts.js)        (posts-exporter.js)
                                          │
                                          ▼
                               ┌─────────────────┐
                               │ 投稿データ取得   │
                               │ (関連テーブル   │
                               │  結合済み)      │
                               └─────────────────┘
                                          │
                                          ▼
                               ┌─────────────────┐
                               │ 設定値確認      │
                               │ (カラム決定)    │
                               └─────────────────┘
                                          │
                                          ▼
                               ┌─────────────────┐
                               │ CSVレスポンス   │
                               │ (post-analytics │
                               │  .YYYY-MM-DD    │
                               │  .csv)          │
                               └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| posts.js | `ghost/core/core/server/api/endpoints/posts.js` | ソース | APIエンドポイント定義 |
| posts-exporter.js | `ghost/core/core/server/services/posts/posts-exporter.js` | ソース | エクスポートロジック本体 |
| posts-service.js | `ghost/core/core/server/services/posts/posts-service.js` | ソース | サービス層（PostsExporterインスタンス化） |
| routes.js | `ghost/core/core/server/web/api/endpoints/admin/routes.js` | ソース | ルーティング定義 |
