hyperscaler-capex ページを「営業CF・CapEx・FCF 三本並列」へ作り直し、合算積み上げ棒と点線位置まで詰める

開発financial-data

/why-hyperscaler-capex-wont-ease-six-pillars の論考を仕上げた直後、画面に並んでいる元データの方の体裁が気になり始めた。/memory-makers/hyperscaler-capex を開き直して MSFT の「24Q2」の数字をじっと見て、フィスカルなのかカレンダーなのか判別できないまま 3 秒固まった。Koyfin の桁とも一発で突き合わせられない。ここから一日かけて、ページの軸とチャートの構成を全部組み替えた。

軸の表記を揃え直す

最初に気持ち悪さを Claude Code に投げた。

マイクロソフトの 24 年 Q2、これフィスカルとカレンダーが混在してて紛らわしい。Koyfin の数字と一発で突き合わせたいので、カレンダーイヤーで統一して、小数点第 1 位まで出してくれ。

ページの内部表現はカレンダー四半期で持っていたが、表示側のラベルが社ごとに揺れていた。MSFT は会計年度終わりが 6 月なので、CY2024Q2 と FY2024Q4 がしょっちゅう取り違えられる。表示ラベルを CY 固定にして、バー上の数字も $57.4B のように小数点 1 桁まで降ろした。Koyfin と画面の数字が縦並びでぴったり重なる状態を作る。

「営業CF・CapEx・FCF」を年度ごとに横並びにする

ここで本題。前日に /hyperscaler-capex-ocf-section-and-koyfin-bug-fix で営業 CF セクションを下に追加したばかりだった。けれど CapEx と営業 CF が別ブロックで上下に並んでいる構成では、各四半期で営業 CF のうち何 % を CapEx に突っ込んだのか、目で追えない。

CapEx のチャートに営業 CF と FCF も並べてくれ。四半期ごとに 3 本のバーで「営業 CF・CapEx・差額の FCF」を横並びに。下の個社別 OCF セクションは削っていい。

Claude Code に HyperscalerCapexSmallMultiples.vue を組み替えさせた。最初の試作は「OCF・CapEx・FCF」の順だったが、画面を見て「左に営業 CF・中央に CapEx・右に FCF」の順に並べ直してもらった。FCF は負の値も取りうるので Y 軸を負側に伸ばし、0 ラインを描き足す。

<!-- 1 ticker × 1 quarter で 3 本バー -->
<rect :x="x(q, 'ocf')"  :height="h(q.ocf)"  fill="var(--ocf)"  />
<rect :x="x(q, 'capex')" :height="h(q.capex)" fill="var(--capex)" />
<rect :x="x(q, 'fcf')"  :height="h(q.fcf)"  :fill="q.fcf >= 0 ? 'var(--fcf+)' : 'var(--fcf-)'" />

3 本バーが並ぶと「四半期内で営業 CF の中から CapEx に何割吸われ、残りどれだけ FCF になったか」が一目で見える。並べただけで意味の取れ方が変わる、というのを画面で実感する瞬間が来た。

Koyfin の「Upgrade」文字列で 5 四半期分が抜けていた

整えたチャートを眺めていて、2023Q1〜2024Q1 のバーがどれも空欄なのに気づいた。これは実績期間のはずなので、Koyfin の数字がそのまま入っているのが正常。

DB を直接覗かせると、eac_quarterly テーブルの該当四半期の metric_value が文字列 "Upgrade" で保存されていた。Koyfin の無料プラン期に過去 4 四半期を引いたとき、プレミアム購読への誘導プレースホルダがそのまま入っていたのが原因。parseFloat("Upgrade")NaNnull で全部抜け落ちている。

ここで「financial_data_quarterly の方は有料版時代に取り込んだ実数が残っているのでは」と勘で投げてみたら当たり。MSFT は FY2015 から全四半期分の実数が綺麗に揃っていた。eac_quarterly で取れない四半期は financial_data_quarterly にフォールバックする生成スクリプトに改修する方針を固める。

Codex レビューで FY → CY マッピングを explicit 表に固定

ここで一度プランを切って codex exec -m gpt-5.5 でレビューを通した。指摘は一発で本質を突いてきた。

fiscalToCalendar を数式で実装すると AAPL/MSFT/ORCL の FY ラベル(会計年度終了年)で 1 年ズレる。Step 1 を「explicit マップ」に必須仕様で固定して、DoD にテストベクトル 6 本を入れろ。

Apple は 9 月締め、Microsoft は 6 月締め、Oracle は 5 月締め。会計年度の終了年で FY を切る会社と、開始年で切る会社が混ざる以上、数式で割り切ると必ずどこかでオフセットを 1 ずらす日が来る。固定表に倒した。

// scripts/lib/hyperscalerFiscalCalendar.mjs(新規)
export const FISCAL_TO_CALENDAR = {
  MSFT: { 'FY24Q1': 'CY23Q3', 'FY24Q2': 'CY23Q4', /* ... */ },
  AAPL: { 'FY24Q1': 'CY23Q4', 'FY24Q2': 'CY24Q1', /* ... */ },
  ORCL: { 'FY24Q1': 'CY23Q3', 'FY24Q2': 'CY23Q4', /* ... */ },
}

Vitest のテストベクトルも 47 本書かせて全部通した。プラン更新後の再レビューでは「コード例が『数式禁止』と矛盾するので削除して固定表だけ残せ」と指摘が入り、これも反映。3 度目のレビューで「致命的な指摘なし、実装に進んで OK」。生成スクリプト 2 本に手を入れて、抜けていた 31 セルが画面で埋まった。

個社の上に「4 社合算積み上げ棒」を新設

実績が埋まったところで、ユーザーとしての興味が次に動いた。

各社の営業 CF・CapEx・FCF を合算して、積み上げ棒で個社別の上に 1 個作ってくれ。あと個社の方は数字が読みづらいので、バーの上にラベル入れて。

HyperscalerCashflowConsolidatedStack.vue を新規で生やした。最初は持っているデータ全社(7 社)で合算したが、Apple の数字を入れたまま画面を見て手が止まる。

Apple ってハイパースケーラーじゃないですよね。合算からは外しましょう。

Apple を除外。続けて Oracle と CoreWeave も「ハイパースケーラーの設備投資余力を見る」という趣旨から外れる規模・性質なので除外。Meta は微妙だが、自社で大規模クラスタを抱えて Llama を回している以上ここでは残す。最終的に合算は MSFT・GOOGL・AMZN・META の 4 社。個社別チャートには 7 社全部残して、参考として並べておく。

点線の境界が CRWV に引きずられていた

最後に細かい違和感。実績と予測を分ける垂直の点線が CY2025Q3 と Q4 の間に引かれていたが、4 社合算で見ると MSFT・GOOGL・AMZN・META は CY2026Q1 まで実績が揃っている。

データを覗くと、点線位置を決めている anyEstimate フラグが「全 7 社の OR」で計算されていた。CRWV だけが CY2025Q4 と CY2026Q1 で isActual=false(estimate)として記録されているので、4 社合算チャートの境界が前倒しに引っ張られていた。

合算チャートでは「対象 ticker の範囲だけで OR を取る」、個社別小チャートでは「その ticker 単体の estimate フラグで判定する」、というように dividerX を ticker スコープに切り直す。CRWV のセルは Q3/Q4 の境、MSFT のセルは Q1/Q2 の境、というように各社で点線位置が独立して動く形に直った。

学び

  • 3 本並列はテーブルより速い。CapEx と営業 CF を別ブロックで上下に置いている間は「Capex / OCF 比率」を別テーブルで計算しないと読めなかったのが、横並びにした瞬間にバーの高さの目算で済むようになった。グラフの軸構成は意味の取れ方を決める。
  • FY → CY 変換は数式で書くと必ず事故る。会計年度の終了年で FY を切る会社と開始年で切る会社が混在する以上、explicit テーブルとテストベクトルで固める。Codex の初手指摘がここを撃ち抜いてきたのは助かった。
  • 「全社 OR」のフラグ集計は対象範囲が動いた瞬間に意味がズレるanyEstimate のような boolean OR は計算範囲をパラメータ化しておく癖をつけるべき。チャートを 7 社→4 社に絞った瞬間、点線が嘘をつき始める典型。
  • 「Upgrade」文字列のような無料プラン由来のプレースホルダは parseFloat で NaN になり、見た目には null と区別がつかない。同じテーブルの他社で値が入っているなら、別テーブルのフォールバックがあるか先に疑う。前日の Koyfin EAC バグ修正と同じ筋の事故。

編集したファイル

  • apps/web/app/components/memory-makers/HyperscalerCapexSmallMultiples.vue(3 本バー化・数値ラベル追加・ticker 別 dividerX)
  • apps/web/app/components/memory-makers/HyperscalerCashflowConsolidatedStack.vue(新規・4 社合算)
  • apps/web/app/pages/memory-makers/hyperscaler-capex.vue(個社別 OCF 節削除・合算節追加・脚注追加)
  • apps/web/app/data/memory-makers/hyperscalerCapexQuarterly.ts / hyperscalerOcfQuarterly.ts(実績 31 セル補填)
  • apps/web/scripts/generate-hyperscaler-capex-quarterly.mjs / generate-hyperscaler-ocf-quarterly.mjsfinancial_data_quarterly フォールバック)
  • apps/web/scripts/lib/hyperscalerFiscalCalendar.mjs(新規・FY→CY explicit 表)
  • apps/web/tests/hyperscaler-fiscal-calendar.test.ts(新規・47 ケース pass)