# 帳票設計書 5-Cost Graph Report (Web UI)

## 概要

本ドキュメントは、opencode Web Console上の月別コスト棒グラフレポートの設計書である。

### 本帳票の処理概要

本帳票は、ワークスペースにおける月別のAI利用コストをモデル別・日別の積み上げ棒グラフで可視化するWebコンポーネントを提供する。Chart.jsを使用したインタラクティブなグラフ表示機能を持つ。

**業務上の目的・背景**：月単位でのコスト推移を視覚的に把握し、予算管理とコスト最適化の意思決定を支援するために必要である。日別・モデル別の内訳を見ることで、コストの増減要因を分析し、適切なモデル選択やリソース配分の判断材料とすることができる。サブスクリプションと従量課金の区別表示により、課金形態ごとのコスト把握も可能。

**帳票の利用シーン**：月次のコストレビュー時、予算策定時、利用パターンの分析時、モデル選定の検討時に利用される。経営層への報告資料作成や、チームリーダーによる利用コストの監視にも有用である。

**主要な出力内容**：
1. 月別日次コスト棒グラフ（モデル別スタック表示）
2. サブスクリプション/従量課金の視覚的区別（透明度による差別化）
3. モデル別凡例（クリックでハイライト）
4. ツールチップによる詳細表示
5. モデル・キー別フィルタリング機能

**帳票の出力タイミング**：ワークスペースページ（`/workspace/[id]`）にアクセスした時点で自動的に表示される。月の選択やフィルタ変更で動的に更新される。

**帳票の利用者**：ワークスペース管理者、経理担当者、プロジェクトマネージャー

## 帳票種別

グラフレポート / コスト分析

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| WS-002 | ワークスペース詳細 | `/workspace/[id]` | ページアクセス時自動表示 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML Canvas（Chart.js） |
| 用紙サイズ | 該当なし |
| 向き | 該当なし |
| ファイル名 | 該当なし |
| 出力方法 | ブラウザ表示（Canvas描画） |
| 文字コード | UTF-8 |

### グラフ仕様

| 項目 | 内容 |
|-----|------|
| グラフタイプ | スタック棒グラフ |
| X軸 | 日付（月内の各日） |
| Y軸 | コスト（ドル） |
| スタック構成 | モデル別 + 課金形態別 |

## 帳票レイアウト

### レイアウト概要

ヘッダー部にフィルタコントロール、メイン部にグラフを表示する。

```
┌─────────────────────────────────────────────────────────────────┐
│  Cost                                                           │
│  View your workspace cost by day.                               │
├─────────────────────────────────────────────────────────────────┤
│  [< January 2026 >]    [All Models ▼]    [All Keys ▼]          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  $100 │     ██                                                  │
│       │    ███     ██                                          │
│  $50  │   ████    ███   ██                                     │
│       │  █████   ████  ███   ██                                │
│  $0   │ ██████  █████ ████  ███                                │
│       └─────────────────────────────────────────                │
│         Jan 01  Jan 02 Jan 03 Jan 04 ...                       │
│                                                                 │
│  [■ claude-sonnet-4] [■ gpt-5] [□ claude-sonnet-4 (Sub)] ...   │
└─────────────────────────────────────────────────────────────────┘
```

### フィルタ部

| No | 項目名 | 説明 | 操作 |
|----|-------|------|------|
| 1 | 月選択 | 表示対象月 | 前月/次月ボタン |
| 2 | モデルフィルタ | 特定モデルのみ表示 | ドロップダウン選択 |
| 3 | キーフィルタ | 特定APIキーのみ表示 | ドロップダウン選択 |

### グラフ部

| No | 項目名 | 説明 | 表示形式 |
|----|-------|------|---------|
| 1 | 棒グラフ | 日別モデル別コスト | 色分けスタック |
| 2 | X軸ラベル | 日付 | `MMM DD`形式（例: Jan 01） |
| 3 | Y軸ラベル | コスト | $x / $x.xk形式 |
| 4 | 凡例 | モデル名 | クリックでハイライト |
| 5 | ツールチップ | 詳細情報 | ホバーで表示 |

### モデル色定義

| モデル名 | カラーコード |
|---------|-------------|
| claude-sonnet-4-5 | #D4745C |
| claude-sonnet-4 | #E8B4A4 |
| claude-opus-4 | #C8A098 |
| claude-haiku-4-5 | #F0D8D0 |
| claude-3-5-haiku | #F8E8E0 |
| gpt-5.1 | #4A90E2 |
| gpt-5.1-codex | #6BA8F0 |
| gpt-5 | #7DB8F8 |
| gpt-5-codex | #9FCAFF |
| gpt-5-nano | #B8D8FF |
| grok-code | #8B5CF6 |
| big-pickle | #10B981 |
| kimi-k2 | #F59E0B |
| qwen3-coder | #EC4899 |
| glm-4.6 | #14B8A6 |
| その他 | HSL(hash % 360, 50%, 65%) |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| workspaceID | ワークスペースID | Yes |
| year | 対象年 | Yes |
| month | 対象月（0-11） | Yes |

### フィルタ条件

| フィルタ | 説明 | デフォルト |
|---------|------|-----------|
| model | モデル名でフィルタ | 全モデル |
| key | APIキーIDでフィルタ | 全キー |

### グラフ表示条件

| 条件 | 動作 |
|-----|------|
| usage.length === 0 | 空状態メッセージ表示 |
| 現在月 | 「次へ」ボタン無効 |
| サブスクリプション利用 | 透明度50%で表示 |

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

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| UsageTable | 利用データ集計 | workspaceID = 指定ID |
| KeyTable | APIキー情報取得 | workspaceID = 指定ID |
| UserTable | キー所有者情報 | KeyTable.userID |
| AuthTable | メールアドレス取得 | UserTable.accountID |

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

#### UsageTable集計クエリ

| 参照項目 | 帳票項目との対応 | 集計方法 | 備考 |
|--------|----------------|---------|------|
| DATE(timeCreated) | X軸ラベル | GROUP BY | 日付抽出 |
| model | スタック分類 | GROUP BY | - |
| SUM(cost) | Y軸値 | SUM | - |
| keyID | キーフィルタ | GROUP BY | - |
| enrichment.plan | サブスクリプション判定 | JSON_EXTRACT | "sub"で判定 |

#### KeyTable結合クエリ

| 参照項目 | 帳票項目との対応 | 取得条件 | 備考 |
|--------|----------------|---------|------|
| id | キーID | - | - |
| name | キー表示名 | - | - |
| userID | ユーザー参照 | - | - |
| timeDeleted | 削除済み判定 | - | null以外は削除済み |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| displayCost | totalCost / 100,000,000 | なし | ドル変換 |
| daysInMonth | new Date(year, month + 1, 0).getDate() | なし | 月の日数取得 |
| modelColor | MODEL_COLORS[model] ?? HSL(hash, 50%, 65%) | なし | モデルごとの色 |
| subOpacity | 0.5 | なし | サブスクリプション透明度 |

### Y軸フォーマット

| 値の範囲 | 表示形式 | 例 |
|---------|---------|-----|
| >= 1000 | $x.xk | $1.5k |
| < 1000 | $x | $500 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[コンポーネントマウント] --> B[createEffect: getCosts呼び出し]
    B --> C[サーバー側: UsageTable集計]
    C --> D[サーバー側: KeyTable取得]
    D --> E[storeにdata設定]
    E --> F[chartConfig計算]
    F --> G{usage.length > 0?}
    G -->|Yes| H[Chart.js描画]
    G -->|No| I[空状態表示]
    H --> J[ユーザー操作待機]
    J --> K{操作種別}
    K -->|月変更| L[onPreviousMonth/onNextMonth]
    K -->|モデル選択| M[onSelectModel]
    K -->|キー選択| N[onSelectKey]
    K -->|凡例ホバー| O[ハイライト処理]
    L --> B
    M --> F
    N --> F
    O --> P[chart.update]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| データなし | usage配列が空 | "No cost data yet." (i18n) | 空状態UIを表示 |
| 認証エラー | withActorで認証失敗 | - | ログイン画面へリダイレクト |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 1ヶ月分（最大31日 x モデル数） |
| 目標出力時間 | 1秒以内 |
| Chart.js更新 | 'none'モードで高速更新 |

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

- withActorによるワークスペースアクセス権限確認
- APIキー情報は表示名のみ（完全キーは非表示）
- 削除済みキーは「(deleted)」表示で識別
- コスト情報はワークスペースメンバーのみ閲覧可能

## 備考

- Chart.jsライブラリ（BarController, BarElement, CategoryScale, LinearScale, Tooltip, Legend）を使用
- ダークモード/ライトモード対応（prefers-color-schemeメディアクエリ監視）
- 凡例ホバーで該当モデルをハイライト、他モデルは透明度低下
- サブスクリプション利用分は「(Sub)」サフィックスと透明度50%で区別
- レスポンシブ対応（maintainAspectRatio: false）

---

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

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

### 推奨読解順序

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

まず、APIから取得されるデータ構造とChart.js設定を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | getCosts関数（27-99行）の戻り値型（usage, keys）を確認 |

**読解のコツ**: Drizzle ORMのクエリビルダーとSQLの対応関係を理解する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | GraphSection関数コンポーネント（150-479行） |

**主要処理フロー**:
1. **151-165行**: 初期化（canvasRef, store, 現在日時）
2. **166-180行**: 月変更ハンドラー
3. **182-193行**: フィルタ計算（getModels, getDates）
4. **207-379行**: chartConfig計算（Chart.js設定生成）

#### Step 3: グラフ設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | chartConfig createMemo（207-379行）でChart.js設定を生成 |

**主要処理フロー**:
- **222-236行**: 日別データマップ構築（サブスクリプション/非サブスクリプション分離）
- **241-269行**: データセット生成（非サブスクリプション → サブスクリプションの順）
- **271-378行**: Chart.jsオプション設定（scales, plugins）

#### Step 4: モデル色定義を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | MODEL_COLORS定数（101-117行）、getModelColor関数（119-125行） |

**主要処理フロー**:
- **101-117行**: 主要モデルの色定義
- **119-125行**: 未定義モデルはハッシュ値からHSL色を生成

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

```
GraphSection (graph-section.tsx:150)
    │
    ├─ createEffect() - データ取得
    │      └─ getCosts(workspaceID, year, month)
    │             │
    │             ├─ Database.use() - UsageTable集計
    │             │      └─ GROUP BY date, model, keyID, subscription
    │             │
    │             └─ Database.use() - KeyTable取得
    │                    └─ JOIN UserTable, AuthTable
    │
    ├─ chartConfig createMemo() - Chart.js設定生成
    │      │
    │      ├─ getDates() - 月内日付配列生成
    │      ├─ getModels() - モデル一覧抽出
    │      ├─ dailyDataSub/NonSub - 日別データマップ
    │      ├─ datasets生成 - 非Sub + Sub
    │      └─ options設定 - scales, plugins, legend
    │
    ├─ createEffect() - Chart描画
    │      └─ new Chart(canvasRef, config)
    │
    └─ createEffect() - ダークモード監視
           └─ prefers-color-scheme media query
```

### データフロー図

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

workspaceID ──────────▶ getCosts()
year, month ──────────▶     │
                            ▼
                    UsageTable集計 ───────▶ usage[]
                            │                  │
                    KeyTable取得 ───────▶ keys[]
                            │                  │
                            └──────────────────┘
                                    │
                                    ▼
                            chartConfig createMemo()
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
             dailyDataSub    dailyDataNonSub    datasets
                    │               │               │
                    └───────────────┴───────────────┘
                                    │
                                    ▼
                            Chart.js描画
                                    │
        ┌───────────┬───────────┬───┴───┬───────────┐
        ▼           ▼           ▼       ▼           ▼
     X軸(日付)  Y軸(コスト)  棒グラフ  凡例    ツールチップ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | ソース | コストグラフコンポーネント |
| graph-section.module.css | `packages/console/app/src/routes/workspace/[id]/graph-section.module.css` | スタイル | コンポーネントスタイル |
| dropdown.tsx | `packages/console/app/src/component/dropdown.tsx` | ソース | ドロップダウンコンポーネント |
| icon.tsx | `packages/console/app/src/component/icon.tsx` | ソース | アイコンコンポーネント |
| auth.withActor.ts | `packages/console/app/src/context/auth.withActor.ts` | ソース | 認証ラッパー |
| i18n.ts | `packages/console/app/src/context/i18n.ts` | ソース | 国際化コンテキスト |
| billing.sql.js | `@opencode-ai/console-core/schema/billing.sql.js` | ソース | UsageTableスキーマ |
| key.sql.js | `@opencode-ai/console-core/schema/key.sql.js` | ソース | KeyTableスキーマ |
