# 帳票設計書 8-Download Stats Report (Markdown)

## 概要

本ドキュメントは、GitHubとnpmのダウンロード統計を追跡するMarkdownレポートの設計書である。

### 本帳票の処理概要

本帳票は、opencodeのGitHubリリースとnpmパッケージのダウンロード統計を日次で収集し、STATS.mdファイルに追記するスクリプトを提供する。累積ダウンロード数と前日比の増減を記録する。

**業務上の目的・背景**：オープンソースプロジェクトの普及状況を定量的に追跡し、成長トレンドの分析とコミュニティへの報告のために必要である。GitHub Releasesとnpmという2つの主要な配布チャネルを統合的に監視することで、プロジェクトの全体的な採用状況を把握できる。また、PostHogへのイベント送信により、分析基盤との連携も実現している。

**帳票の利用シーン**：日次の自動実行（CI/CD）による統計更新、プロジェクト進捗報告、成長分析、コミュニティへの透明性確保のために利用される。マーケティングや投資家向け報告の基礎データとしても活用可能。

**主要な出力内容**：
1. 日付（実行日）
2. GitHubダウンロード数（累積 + 増減）
3. npmダウンロード数（累積 + 増減）
4. 合計ダウンロード数（累積 + 増減）

**帳票の出力タイミング**：`bun script/stats.ts` コマンドを実行した時点で出力される。通常は日次バッチ（GitHub Actions等）で自動実行される。

**帳票の利用者**：プロジェクト管理者、オープンソースコントリビューター、マーケティング担当者

## 帳票種別

統計レポート / ダウンロード追跡

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | CLI / CI | `bun script/stats.ts` | コマンド実行 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | Markdown (.md) |
| 用紙サイズ | 該当なし |
| 向き | 該当なし |
| ファイル名 | STATS.md（プロジェクトルート） |
| 出力方法 | ファイル追記 |
| 文字コード | UTF-8 |

### 出力フォーマット

```markdown
# Download Stats

| Date       | GitHub Downloads     | npm Downloads        | Total                 |
| ---------- | -------------------- | -------------------- | --------------------- |
| 2026-01-01 | 1,000,000 (+10,000)  | 500,000 (+5,000)     | 1,500,000 (+15,000)   |
| 2026-01-02 | 1,015,000 (+15,000)  | 508,000 (+8,000)     | 1,523,000 (+23,000)   |
| ...        | ...                  | ...                  | ...                   |
```

## 帳票レイアウト

### レイアウト概要

Markdownテーブル形式で日次統計を記録する。

```
┌──────────────────────────────────────────────────────────────────────────┐
│                           # Download Stats                              │
├──────────────────────────────────────────────────────────────────────────┤
│  | Date       | GitHub Downloads   | npm Downloads    | Total           │
│  |------------|-------------------|------------------|-----------------|
│  | 2026-01-01 | 1,000,000 (+10K)  | 500,000 (+5K)   | 1,500,000 (+15K)│
│  | ...        | ...               | ...              | ...             │
└──────────────────────────────────────────────────────────────────────────┘
```

### テーブル列定義

| No | 列名 | 説明 | データ取得元 | 表示形式 |
|----|-----|------|-------------|---------|
| 1 | Date | 記録日 | 実行日時 | YYYY-MM-DD |
| 2 | GitHub Downloads | GitHubリリースダウンロード | GitHub API | 累積値 (+増減) |
| 3 | npm Downloads | npmダウンロード | npm API | 累積値 (+増減) |
| 4 | Total | 合計 | 計算値 | 累積値 (+増減) |

## 出力条件

### 実行条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| Bun runtime | Bunランタイム環境 | Yes |
| ネットワーク接続 | GitHub/npm APIアクセス | Yes |
| POSTHOG_KEY | PostHog APIキー（オプション） | No |

### データ取得範囲

| データソース | 取得範囲 | APIエンドポイント |
|-------------|---------|------------------|
| GitHub Releases | 全リリース（ページネーション） | `api.github.com/repos/anomalyco/opencode/releases` |
| npm Downloads | 2020-01-01〜現在+5年 | `api.npmjs.org/downloads/range/{range}/opencode-ai` |

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

### 外部API参照

#### GitHub Releases API

| エンドポイント | `https://api.github.com/repos/anomalyco/opencode/releases` |
|---------------|-----|
| メソッド | GET |
| ページネーション | page, per_page (100件/ページ) |
| レート制限 | 1秒待機/ページ |

**レスポンス構造**:
```typescript
interface Release {
  tag_name: string
  name: string
  assets: Array<{
    name: string
    download_count: number
  }>
}
```

#### npm Downloads API

| エンドポイント | `https://api.npmjs.org/downloads/range/{start}:{end}/{package}` |
|---------------|-----|
| メソッド | GET |
| パッケージ名 | opencode-ai |
| 期間 | 2020-01-01〜{currentYear + 5}-12-31 |

**レスポンス構造**:
```typescript
interface NpmDownloadsRange {
  start: string
  end: string
  package: string
  downloads: Array<{
    downloads: number
    day: string
  }>
}
```

### 既存ファイル参照

| ファイル | 用途 |
|---------|------|
| STATS.md | 前回値の取得、追記先 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| githubTotal | SUM(release.assets[].download_count) | なし | 全リリースの全アセット合計 |
| npmDownloads | SUM(downloads[].downloads) | なし | 全日のダウンロード合計 |
| total | githubTotal + npmDownloads | なし | 総合計 |
| githubChange | githubTotal - previousGithub | なし | 前日比増減 |
| npmChange | npmDownloads - previousNpm | なし | 前日比増減 |
| totalChange | total - previousTotal | なし | 前日比増減 |

### 増減表示フォーマット

| 条件 | 表示形式 | 例 |
|-----|---------|-----|
| change > 0 | `(+{change.toLocaleString()})` | (+10,000) |
| change < 0 | `({change.toLocaleString()})` | (-1,000) |
| change === 0 | `(+0)` | (+0) |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[スクリプト実行] --> B[fetchReleases: GitHub API呼び出し]
    B --> C{全ページ取得完了?}
    C -->|No| D[次ページ取得]
    D --> E[1秒待機]
    E --> C
    C -->|Yes| F[calculate: ダウンロード数集計]
    F --> G[fetchNpmDownloads: npm API呼び出し]
    G --> H[save: STATS.md更新]
    H --> I[既存ファイル読み込み]
    I --> J[前回値パース]
    J --> K[新しい行生成]
    K --> L[ファイル追記]
    L --> M[prettier整形]
    M --> N[sendToPostHog: GitHub統計送信]
    N --> O[sendToPostHog: npm統計送信]
    O --> P[コンソール出力]
    P --> Q[終了]
```

### PostHog連携フロー

```mermaid
flowchart TD
    A[sendToPostHog呼び出し] --> B{POSTHOG_KEY存在?}
    B -->|No| C[警告出力・スキップ]
    B -->|Yes| D[fetch POST]
    D --> E{成功?}
    E -->|Yes| F[完了]
    E -->|No| G[エラー警告]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| GitHub API エラー | レスポンスがok以外 | `GitHub API error: {status} {statusText}` | 例外スロー |
| npm API エラー | レスポンスがok以外 | `Failed to fetch npm downloads: {status}` | 警告出力、0を返す |
| npm API例外 | fetch失敗 | `Error fetching npm downloads:` | 警告出力、0を返す |
| PostHog APIエラー | レスポンスがok以外 | `PostHog API error: {status}` | 警告出力、処理継続 |
| PostHog未設定 | 環境変数なし | `POSTHOG_API_KEY not set, skipping PostHog event` | 警告出力、スキップ |
| ファイル読み込みエラー | STATS.md不存在 | - | 新規ヘッダー生成 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| GitHub APIページネーション | 100件/ページ、1秒待機/ページ |
| npm API | 単一リクエスト（全期間） |
| 想定実行時間 | 数秒〜数十秒（リリース数依存） |

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

- POSTHOG_KEYは環境変数で管理（ハードコード禁止）
- GitHub APIはパブリックアクセス（認証不要）
- npm APIはパブリックアクセス（認証不要）
- 出力ファイルは公開リポジトリに含まれる想定

## 備考

- Bunランタイム専用（`#!/usr/bin/env bun`）
- Bun.file/Bun.write/Bun.spawnを使用
- prettier --writeで出力後にフォーマット整形
- 将来の期間拡張を考慮し、endYearはcurrentYear + 5で設定
- 既存STATS.mdの最後の行から前回値をパース

---

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

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

### 推奨読解順序

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

まず、外部APIから取得するデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | stats.ts | `script/stats.ts` | Asset, Release, NpmDownloadsRange interface（31-49行） |

**読解のコツ**: 各APIのレスポンス構造がinterfaceで定義されているため、そこからデータフローを理解する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | stats.ts | `script/stats.ts` | スクリプトメイン処理（191-226行）- 処理順序の確認 |

**主要処理フロー**:
1. **191-194行**: fetchReleases呼び出し
2. **196行**: calculate呼び出し
3. **198-200行**: fetchNpmDownloads呼び出し
4. **202行**: save呼び出し
5. **204-212行**: sendToPostHog呼び出し

#### Step 3: GitHub API連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | stats.ts | `script/stats.ts` | fetchReleases関数（70-95行）のページネーション処理 |

**主要処理フロー**:
- **76-78行**: APIエンドポイント構築
- **83-89行**: ページネーションループ
- **91行**: 1秒待機

#### Step 4: npm API連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | stats.ts | `script/stats.ts` | fetchNpmDownloads関数（52-68行）の期間指定 |

**主要処理フロー**:
- **55-57行**: 期間範囲設定（2020〜currentYear+5）
- **63行**: downloads配列の合計計算

#### Step 5: ファイル出力を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | stats.ts | `script/stats.ts` | save関数（125-189行）のファイル読み書き |

**主要処理フロー**:
- **136-153行**: 既存ファイルから前回値パース
- **158-175行**: 増減値計算
- **176行**: 新規行生成
- **183-184行**: ファイル書き込み + prettier整形

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

```
main (stats.ts:191)
    │
    ├─ fetchReleases() - GitHub APIからリリース一覧取得
    │      │
    │      └─ fetch() - ページネーション（100件/ページ、1秒待機）
    │
    ├─ calculate(releases) - ダウンロード数集計
    │      │
    │      └─ SUM(assets[].download_count)
    │
    ├─ fetchNpmDownloads("opencode-ai") - npm APIから取得
    │      │
    │      └─ fetch() → SUM(downloads[].downloads)
    │
    ├─ save(githubTotal, npmDownloads) - STATS.md更新
    │      │
    │      ├─ Bun.file().text() - 既存ファイル読み込み
    │      │
    │      ├─ 前回値パース（正規表現）
    │      │
    │      ├─ 増減計算
    │      │
    │      ├─ Bun.write() - ファイル追記
    │      │
    │      └─ Bun.spawn(["bunx", "prettier", "--write"]) - 整形
    │
    └─ sendToPostHog() x 2 - 統計イベント送信
           │
           └─ fetch() - POST to us.i.posthog.com
```

### データフロー図

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

GitHub API ───────────▶ fetchReleases()
(releases)                    │
                              ▼
                    calculate() ────────────▶ githubTotal
                              │
npm API ──────────────▶ fetchNpmDownloads() ──▶ npmDownloads
(downloads)                   │
                              │
STATS.md ─────────────▶ 前回値パース ───────────▶ previous*
(既存ファイル)                │
                              │
                    ┌─────────┴──────────┐
                    ▼                    ▼
               増減計算              新規行生成
                    │                    │
                    └─────────┬──────────┘
                              ▼
                    STATS.md追記
                              │
                              ▼
                    prettier整形
                              │
                              ▼
                    PostHog送信
                              │
        ┌─────────────────────┴─────────────────────┐
        ▼                                           ▼
    GitHub統計                                   npm統計
    (source: "github")                      (source: "npm")
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| stats.ts | `script/stats.ts` | ソース | 統計収集スクリプト本体 |
| STATS.md | `STATS.md` | 出力 | 統計データMarkdownファイル |
| package.json | `package.json` | 設定 | Bunスクリプト定義 |

### STATS.md出力例

実際のファイル内容（抜粋）:

```markdown
# Download Stats

| Date       | GitHub Downloads     | npm Downloads        | Total                 |
| ---------- | -------------------- | -------------------- | --------------------- |
| 2025-06-29 | 18,789 (+0)          | 39,420 (+0)          | 58,209 (+0)           |
| 2025-06-30 | 20,127 (+1,338)      | 41,059 (+1,639)      | 61,186 (+2,977)       |
| ...        | ...                  | ...                  | ...                   |
| 2026-01-29 | 7,815,471 (+326,101) | 2,374,982 (+60,133)  | 10,190,453 (+386,234) |
```
