• #refactoring
  • #vue
  • #nvidia
  • #financial-quiz
開発financial-data完了

NVIDIA EPS予測ページ リファクタリング計画

新規セッションへの指示(最重要)

Claude Codeへ: このドキュメントを読んでいる場合、以下の手順で作業を進めてください。

1. 現状確認

# メインファイルは読み込み不可(34,075トークン > 25,000上限)
# 部分読み込みで対応

Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=1 limit=100
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=950 limit=100

2. 進捗確認

このドキュメントの「完了ステータス」セクションを確認し、未完了のPhaseから作業開始。

3. 作業時のルール

  • 1つのPhaseが完了したら必ず「完了ステータス」を更新
  • 問題が発生したら「作業ログ」に記録
  • 動作確認はChrome DevTools MCPで実施

4. 作業完了後

このドキュメントの「完了ステータス」と「作業ログ」を更新してからセッション終了。


クイックスタート

このドキュメントの目的

Claude Codeで読み込めないほど大きいnvidia-eps-forecast.vue(2586行)を、切り出し済みコンポーネントを活用してリファクタリングする。

作業開始コマンド

cd apps/web
pnpm dev

開発サーバー起動後、http://localhost:3000/financial-quiz/nvidia-eps-forecast で動作確認。

メインファイルの部分読み込み方法

# template部分(感応度パネル)
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=1 limit=100

# template部分(チャート)
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=80 limit=200

# script部分(import)
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=950 limit=50

# script部分(composable使用)
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=1414 limit=50

# script部分(forecastData)
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=1550 limit=100

# style部分
Read apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue offset=2009 limit=100

進捗トラッキング(重要)

セッション間での引き継ぎ方法

新規セッションでの開始手順:

  1. このドキュメントを読む: Read apps/web/content/2025-12-18/nvidia-eps-forecast-refactoring-plan.md
  2. 下の「完了ステータス」を確認
  3. 未完了のPhaseから作業再開

完了ステータス

各Phase完了時に [ ][x] に更新してください。

Phase 1: script部分の整理
  [x] 1-1. importの整理
  [x] 1-2. composable呼び出しの統一
  [x] 1-3. 重複ロジックの削除
  [x] 1-4. 動作確認

Phase 2: SensitivityPanelの統合
  [x] 2-1. コンポーネント置き換え
  [x] 2-2. スタイル削除
  [x] 2-3. 動作確認

Phase 3: ForecastSummaryの統合
  [x] 3-1. コンポーネント置き換え
  [x] 3-2. スタイル削除
  [x] 3-3. 動作確認

Phase 4: DualSeriesChartの統合
  [x] 4-1. データ変換computed追加
  [x] 4-2. 年間EPSチャート置き換え
  [x] 4-3. 年間売上チャート置き換え
  [x] 4-4. 四半期EPSチャート置き換え
  [x] 4-5. 四半期売上チャート置き換え
  [x] 4-6. 動作確認

Phase 5: ChartModal新規作成
  [x] 5-1. ChartModal.vue作成
  [x] 5-2. メインファイルのモーダル部分を置き換え
  [x] 5-3. 動作確認

Phase 6: 最終クリーンアップ
  [x] 6-1. 不要なスタイル削除
  [x] 6-2. 全体動作確認
  [x] 6-3. ファイルサイズ確認(最終: 630行、77%削減)

Phase 7: チャート表示修正
  [x] 7-1. 4つのチャートに成長率ライン(折れ線グラフ)のデータラベルを追加
        - 年間EPSチャート(YoY成長率%)
        - 年間売上高チャート(YoY成長率%)
        - 四半期EPSチャート(QoQ成長率%)
        - 四半期売上チャート(QoQ成長率%)
  [x] 7-2. 実績/予測の区切り線の位置修正(FY25とFY26の間に移動)
        - FY26の最後の四半期(4Q'26)が予測なので、FY25が最後の完全実績年度
        - getDividerX computed追加で中間位置を計算
  [x] 7-3. QoQオーバーライドの初期値修正
        - EPSベース→売上ベースに変更(calculateAnalystQoQRates関数を修正)
        - 4Q'26: 12.3%→14.9%(売上QoQ)に修正
  [x] 7-4. 動作確認
  [x] 7-5. QoQオーバーライドを年間サマリーに連動
        - annualForecastDataがスライダー値から独立計算していた問題を修正
        - quarterlyForecastDataから年度別売上を集計するように変更
        - QoQ変更時にサマリー(上昇率、CAGR)が連動更新されるように
  [x] 7-6. QoQオーバーライドテーブルを横向きに変更
        - 四半期を列、アナリスト予測/修正版を行に配置
        - アナリスト予測がない期間(4Q'28以降)は「-」表示、修正版は10.7%で編集可能
        - 年間売上成長率スライダーを削除(不要になったため)

Phase 8: 計算詳細テーブルの追加
  [x] 8-1. QoQオーバーライドテーブルのカラムをFY28までに制限
        - FY29以降(1Q'29~4Q'30)のカラムを削除
        - 予測期間をFY30まで(FY31削除)に変更
        - FY29以降は一律10.7%を適用(内部計算のみ、UI表示なし)

  ※ Phase 8-2以降は別ドキュメントに移行:
    content/2025-12-18/nvidia-eps-forecast-phase8-calculation-table.md

作業ログ

各セッションで作業した内容を記録してください:

[2025-12-18 セッション1]
- 計画ドキュメント作成
- 現状分析完了

[2025-12-18 セッション2]
- Phase 1 完了: script部分の整理
  - eacLightData importを削除(composable内で使用)
  - composable変数のエイリアス(composableGrowthRate等)を削除し直接使用
  - nvdaData重複取得を削除
  - ローカルquarterlyForecastData computedを削除(composable版を使用)
  - forecastDataをannualForecastDataベースに変更しYoY計算を追加
- ファイルサイズ: 2754行 → 2596行(158行削減)
- ブラウザ動作確認: 全機能正常動作

[2025-12-18 セッション3]
- Phase 2 完了: SensitivityPanelの統合
- Phase 3 完了: ForecastSummaryの統合
- Phase 4 完了: DualSeriesChartの統合(4チャート全て置き換え)
- ファイルサイズ: 2596行 → 1619行(38%削減)

[2025-12-18 セッション4]
- Phase 5 完了: ChartModal新規作成
  - ChartModal.vue作成(~580行、4種類のチャート対応)
  - メインファイルのTeleportモーダル部分を置き換え
  - ESCキーハンドリングをChartModal内に移動
- ファイルサイズ: 1619行 → 1094行(32%削減)

[2025-12-18 セッション5]
- Phase 6 完了: 最終クリーンアップ
  - 未使用のチャート定数削除(chartWidth, chartHeight, padding等)
  - 未使用のチャート計算関数・computed削除(~345行)
  - 未使用のスタイル削除(chart-section, legend関連、expand-hint等)
  - 未使用のimport削除(formatQuarterLabel)
- 最終ファイルサイズ: 1094行 → 630行
- 総削減: 2754行 → 630行(77%削減、2124行削減)
- ブラウザ動作確認: 全機能正常動作(モーダル含む)

[2025-12-18 セッション6]
- Phase 7 完了: チャート表示修正
  - 7-1: DualSeriesChart.vueに成長率ラインのデータラベル追加
    - アナリスト成長率(青):ライン上部に%表示
    - 修正版成長率(赤):ライン下部に%表示
  - 7-2: 区切り線位置をFY25とFY26の中間に修正
    - getDividerX computed追加
  - 7-3: QoQ初期値計算をEPSベース→売上ベースに変更
    - useNvidiaForecast.tsのcalculateAnalystQoQRates関数を修正
    - 4Q'26のQoQ: 12.3%→14.9%に修正
  - 7-4: ブラウザ動作確認完了
  - 7-5: QoQオーバーライドを年間サマリーに連動
    - 問題: annualForecastDataがスライダー値から独立計算、QoQ変更が反映されず
    - 解決: quarterlyForecastDataから年度別売上を集計するaggregateByFiscalYear関数追加
    - 検証: QoQ 14.9%→30%変更時にサマリーが$647→$732、CAGR 24.3%→26.9%に連動更新

[2025-12-18 セッション7]
- Phase 7-6 完了: QoQオーバーライドテーブル横向き変更
  - SensitivityPanel.vue: テーブルレイアウト変更(四半期=列、アナリスト/修正版=行)
  - 年間売上成長率スライダー削除(不要)
- Phase 8-1 完了: 予測期間の調整
  - useNvidiaForecast.ts: calculateDisplayQoQRates関数作成
  - QoQオーバーライドUI表示をFY28まで(4Q'26〜4Q'28)に制限
  - 予測期間をFY30まで(FY31削除)に変更
  - FY29〜30は内部計算で一律10.7% QoQを使用
- Phase 8用の新規ドキュメント作成
  - nvidia-eps-forecast-phase8-calculation-table.md

ファイル構成

メインファイル(リファクタリング完了)

apps/web/app/pages/financial-quiz/nvidia-eps-forecast.vue
  • 630行(リファクタリング前: 2754行、77%削減)

切り出し済みコンポーネント(使用中)

apps/web/app/components/financial-quiz/nvidia-eps-forecast/
├── index.ts              (7行)   - エクスポート定義
├── types.ts              (90行)  - 型定義
├── useNvidiaForecast.ts  (303行) - ビジネスロジック
├── DualSeriesChart.vue   (473行) - 汎用チャートコンポーネント
├── SensitivityPanel.vue  (214行) - 感応度パネル
├── ForecastSummary.vue   (113行) - サマリーカード
└── ChartModal.vue        (580行) - 拡大モーダル(新規作成)

メインファイルの構造マップ

template部分(1-950行頃)

行番号内容対応コンポーネント
1-22ページヘッダー、イントロなし(残す)
24-79感応度パネル(スライダー4つ)SensitivityPanel.vue
82-230年間EPSチャート(SVG直書き)DualSeriesChart.vue
232-380年間売上チャートDualSeriesChart.vue
382-600四半期EPSチャートDualSeriesChart.vue
602-780四半期売上チャートDualSeriesChart.vue
782-850サマリーセクションForecastSummary.vue
852-950モーダル(4チャート拡大表示)新規ChartModal.vue

script部分(950-2007行)

行番号内容状態
950-970import文useNvidiaForecastインポート済み
970-1000composableからの変数取得部分的に使用
1000-1420チャート座標計算(computed多数)重複
1414-1423composable変数のエイリアス中途半端
1425-1700forecastData計算ロジックcomposableと重複
1700-2007追加のcomputed、ユーティリティ一部残す必要あり

style部分(2009-2586行)

  • 約577行のスタイル
  • 各コンポーネントに移動済みのものと重複あり

各コンポーネントの詳細

1. useNvidiaForecast.ts

パス: apps/web/app/components/financial-quiz/nvidia-eps-forecast/useNvidiaForecast.ts

返り値:

return {
  // パラメータ(ref)
  annualGrowthRate,      // ref(50) - 年間成長率 10-100%
  netProfitMargin,       // ref(55) - 純利益率 40-70%
  perMultiple,           // ref(25) - PER倍率 15-45x
  dilutionRate,          // ref(2.5) - 希釈率 0-5%
  currentStockPrice,     // ref(175) - 現在株価
  targetQoQGrowth,       // computed - QoQ成長率

  // データ
  historicalQuarters,    // computed - 過去8四半期履歴
  historicalAvgGrowth,   // computed - 過去平均成長率
  historicalAvgMargin,   // computed - 過去平均利益率
  quarterlyForecastData, // computed - 四半期予測データ
  annualForecastData,    // computed - 年間予測データ

  // サマリー
  finalYear,             // computed - 最終年度データ
  yearsToFinal,          // computed - 最終年度までの年数
  epsDiff,               // computed - EPS差異%
  stockDiff,             // computed - 株価差異%
  finalModifiedStockPrice, // computed - 修正版最終株価
  upside,                // computed - 上昇率%
  cagr                   // computed - CAGR%
}

2. SensitivityPanel.vue

パス: apps/web/app/components/financial-quiz/nvidia-eps-forecast/SensitivityPanel.vue

Props:

interface Props {
  annualGrowthRate: number
  netProfitMargin: number
  perMultiple: number
  dilutionRate: number
  currentStockPrice: number
  targetQoQGrowth: number
  historicalAvgGrowth: number
  historicalAvgMargin: number
}

Emits:

'update:annualGrowthRate': [value: number]
'update:netProfitMargin': [value: number]
'update:perMultiple': [value: number]
'update:dilutionRate': [value: number]

使用例:

<SensitivityPanel
  v-model:annualGrowthRate="annualGrowthRate"
  v-model:netProfitMargin="netProfitMargin"
  v-model:perMultiple="perMultiple"
  v-model:dilutionRate="dilutionRate"
  :currentStockPrice="currentStockPrice"
  :targetQoQGrowth="targetQoQGrowth"
  :historicalAvgGrowth="historicalAvgGrowth"
  :historicalAvgMargin="historicalAvgMargin"
/>

3. ForecastSummary.vue

パス: apps/web/app/components/financial-quiz/nvidia-eps-forecast/ForecastSummary.vue

Props:

interface Props {
  epsDiff: number
  stockDiff: number
  upside: number
  cagr: number
  currentStockPrice: number
  finalModifiedStockPrice: number
  yearsToFinal: number
}

使用例:

<ForecastSummary
  :epsDiff="epsDiff"
  :stockDiff="stockDiff"
  :upside="upside"
  :cagr="cagr"
  :currentStockPrice="currentStockPrice"
  :finalModifiedStockPrice="finalModifiedStockPrice"
  :yearsToFinal="yearsToFinal"
/>

4. DualSeriesChart.vue

パス: apps/web/app/components/financial-quiz/nvidia-eps-forecast/DualSeriesChart.vue

Props:

interface Props {
  title: string
  metricLabel: string
  data: DualSeriesDataItem[]
  xAxisLabel?: string       // default: '期間'
  yAxisLabel?: string       // default: '値'
  growthAxisLabel?: string  // default: '成長率 (%)'
  growthLabel?: string      // default: '成長率'
  showGrowthLine?: boolean  // default: false
  isEps?: boolean           // default: false
  isQuarterly?: boolean     // default: false
}

DualSeriesDataItem型(types.tsで定義):

interface DualSeriesDataItem {
  label: string
  isActual: boolean
  hasAnalystData: boolean
  analystValue: number | null
  modifiedValue: number
  analystGrowth?: number | null
  modifiedGrowth?: number | null
}

Emits:

'chart-click': []

実装手順(Phase別)

Phase 1: script部分の整理

目標: 重複ロジックを削除し、composableを完全活用

手順:

  1. メインファイルの950-1000行を確認(import部分)
  2. 以下のimportを追加/確認:
import {
  useNvidiaForecast,
  SensitivityPanel,
  ForecastSummary,
  DualSeriesChart
} from '~/components/financial-quiz/nvidia-eps-forecast'
  1. composableの呼び出し:
const {
  annualGrowthRate,
  netProfitMargin,
  perMultiple,
  dilutionRate,
  currentStockPrice,
  targetQoQGrowth,
  historicalQuarters,
  historicalAvgGrowth,
  historicalAvgMargin,
  quarterlyForecastData,
  annualForecastData,
  epsDiff,
  stockDiff,
  finalModifiedStockPrice,
  upside,
  cagr,
  yearsToFinal
} = useNvidiaForecast()
  1. 削除対象(composableと重複):
    • 1414-1423行: composable変数のエイリアス
    • 1425-1700行: forecastData計算ロジック
    • 1000-1420行の大部分: チャート座標計算
  2. 残す必要があるもの:
    • モーダル制御(modalChart, openModal, closeModal)
    • チャート用のデータ変換computed

Phase 2: SensitivityPanelの統合

削除対象: メインファイル24-79行

置き換え:

<!-- 削除: 24-79行の<div class="sensitivity-panel">...</div> -->

<!-- 追加 -->
<SensitivityPanel
  v-model:annualGrowthRate="annualGrowthRate"
  v-model:netProfitMargin="netProfitMargin"
  v-model:perMultiple="perMultiple"
  v-model:dilutionRate="dilutionRate"
  :currentStockPrice="currentStockPrice"
  :targetQoQGrowth="targetQoQGrowth"
  :historicalAvgGrowth="historicalAvgGrowth"
  :historicalAvgMargin="historicalAvgMargin"
/>

Phase 3: ForecastSummaryの統合

削除対象: メインファイル782-850行付近のサマリーセクション

置き換え:

<ForecastSummary
  :epsDiff="epsDiff"
  :stockDiff="stockDiff"
  :upside="upside"
  :cagr="cagr"
  :currentStockPrice="currentStockPrice"
  :finalModifiedStockPrice="finalModifiedStockPrice"
  :yearsToFinal="yearsToFinal"
/>

Phase 4: DualSeriesChartの統合

追加が必要: データ変換computed

// 年間EPSチャート用データ変換
const annualEpsChartData = computed((): DualSeriesDataItem[] => {
  return annualForecastData.value.map(item => ({
    label: item.year,
    isActual: item.isActual,
    hasAnalystData: item.analystEps !== null,
    analystValue: item.analystEps,
    modifiedValue: item.modifiedEps,
    analystGrowth: null, // YoYが必要なら計算
    modifiedGrowth: null
  }))
})

// 年間売上チャート用
const annualRevenueChartData = computed((): DualSeriesDataItem[] => {
  return annualForecastData.value.map(item => ({
    label: item.year,
    isActual: item.isActual,
    hasAnalystData: item.analystRevenue !== null,
    analystValue: item.analystRevenue ? item.analystRevenue / 1000 : null, // Billionsに変換
    modifiedValue: item.modifiedRevenue / 1000,
    analystGrowth: null,
    modifiedGrowth: null
  }))
})

// 四半期も同様に変換

置き換え:

<DualSeriesChart
  title="年間EPS予測チャート"
  metricLabel="EPS"
  :data="annualEpsChartData"
  xAxisLabel="会計年度"
  yAxisLabel="EPS ($)"
  :isEps="true"
  @chart-click="openModal('annual-eps')"
/>

Phase 5: ChartModal新規作成

新規ファイル: apps/web/app/components/financial-quiz/nvidia-eps-forecast/ChartModal.vue

基本構造:

<template>
  <div v-if="open" class="modal-overlay" @click.self="close">
    <div class="modal-content">
      <button class="close-btn" @click="close">×</button>
      <DualSeriesChart
        :title="chartTitle"
        :data="chartData"
        :metricLabel="metricLabel"
        v-bind="chartProps"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
import DualSeriesChart from './DualSeriesChart.vue'
import type { DualSeriesDataItem } from './types'

interface Props {
  open: boolean
  chartType: 'annual-eps' | 'annual-revenue' | 'quarterly-eps' | 'quarterly-revenue' | null
  // 各チャートタイプに対応するデータ
  annualEpsData?: DualSeriesDataItem[]
  annualRevenueData?: DualSeriesDataItem[]
  quarterlyEpsData?: DualSeriesDataItem[]
  quarterlyRevenueData?: DualSeriesDataItem[]
}

const props = defineProps<Props>()
const emit = defineEmits<{ 'update:open': [value: boolean] }>()

const close = () => emit('update:open', false)

// chartTypeに応じてデータとpropsを切り替え
const chartData = computed(() => { /* ... */ })
const chartTitle = computed(() => { /* ... */ })
</script>

動作確認チェックリスト

各Phase完了後に確認

  • スライダー4つが動作する
  • スライダー変更でチャートが更新される
  • サマリーカードの数値が更新される
  • チャートクリックでモーダルが開く
  • モーダル内のチャートが正しく表示される
  • レスポンシブ(モバイル)で崩れない

ブラウザ確認コマンド

# Chrome DevTools MCP使用
start "" "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="%TEMP%\chrome-profile-stable" "http://localhost:3000/financial-quiz/nvidia-eps-forecast"

注意点・トラブルシューティング

1. composableのref vs computed

  • annualGrowthRate等はrefなので.valueで読み書き可能
  • v-modelで直接バインド可能

2. データ型の違い

メインファイルのforecastDataとcomposableのannualForecastDataで型が微妙に異なる可能性:

メインファイル(ForecastItem):

interface ForecastItem {
  year: string
  analystEpsYoY: number | null  // YoYを含む
  modifiedEpsYoY: number
  // ...
}

composable(AnnualForecastItem):

interface AnnualForecastItem {
  year: string
  // YoYは含まない
  // ...
}

→ YoYが必要な場合はデータ変換computedで計算

3. モーダルのチャートサイズ

DualSeriesChart.vueisQuarterlyでサイズが変わる:

  • isQuarterly: false → chartWidth: 800
  • isQuarterly: true → chartWidth: 1200

モーダル用に別のサイズが必要な場合はpropsを追加


完了後の予想ファイルサイズ

ファイルBeforeAfter
nvidia-eps-forecast.vue2586行〜200-300行
useNvidiaForecast.ts303行〜350行(YoY計算追加)
ChartModal.vueなし〜150行
変更なし-

トークン数: 34,075 → 〜3,000(読み込み可能に)


新規セッション用プロンプト(コピペ用)

新規セッションを開始する際、以下をコピペしてください:

nvidia-eps-forecast.vueのリファクタリングを続けてください。

計画ドキュメント:
apps/web/content/2025-12-18/nvidia-eps-forecast-refactoring-plan.md

このドキュメントを読んで、「完了ステータス」を確認し、未完了のPhaseから作業を再開してください。
作業が完了したら、ドキュメントの「完了ステータス」と「作業ログ」を更新してください。