何をやったか
/beat-monitoring/scatter は 13 銘柄を「Forward P/E × NTM EPS 成長率」の平面にプロットしている自作の散布図ページで、PEG(成長率÷PER)を 2 軸に開いた図と同じ意味を持つ。ここに「指標基準」のトグルを足し、軸の計算式を EPS 基準 ↔ FCF 基準 で差し替えられるようにした。BCU の Yartseva 2025 が 464 銘柄・2009〜2024 の米国 10 倍株分析で「FCF 利回りが主要駆動要因」と特定していたのを取り込みたかった。
実装の前日(2026-06-15)に Phase 4 の純粋関数と scatter.vue 側のトグル UI、E2E スモークまでを一気に投入していた。ところが翌朝(2026-06-16)に積み残しを点検したら、BeatValuation 型に FCF フィールドを足し忘れていたのが見つかった。スクリプト側のテンプレートには入っているのに、実体の valuation.ts の interface だけ古いまま残っていて、データ行には ntmFcfps が入っているのに型では存在しない、というねじれが起きていた。
朝の点検で見つけた型のねじれ
claude-code-sync のログに「積み残しを教えて」と打ったら、3 本のうち 1 本としてこの計画書が返ってきた。実装は完了していると Claude Code は答えたが、念のため valuation.ts をフルで開いて 23-38 行目あたりを目視確認させた。
interface BeatValuation の中に ntmEps, ltmEps, ntmEpsGrowth, ntmPe などは並んでいるのに、ntmFcfps, ltmFcfps, ntmFcfGrowth, ntmFcfYield の 4 つだけ抜けていた。一方でファイル下部のデータ行のほうには FCF の数値が入っていた。スクリプト側のテンプレートを Phase 2 で書き換えたのは正しいのに、Phase 3 で interface を書き換えるはずだったコミット(82117e63)に拾われていなかった。
ねじれが残っていた理由は明確で、現スナップショットの 30 銘柄中 10 銘柄しか FCF を持たない部分充足状態だった。required にすると残り 20 銘柄の null/undefined で型エラーが噴き出す。optional + nullable で 4 フィールドを足すのが筋だった。
修正の方針
interface の追補は 4 行で済んだ。
// 既存
ntmEps: number | null
ltmEps: number | null
ntmEpsGrowth: number | null
ntmPe: number | null
// 追加(optional + nullable で後方互換)
ntmFcfps?: number | null
ltmFcfps?: number | null
ntmFcfGrowth?: number | null
ntmFcfYield?: number | null
optional を選んだのは「将来 generate-beat-valuation.mjs を再実行すれば、データのない銘柄も null で全銘柄分が揃う」状態に持っていけるから。required に倒すと一時的な不整合期間に TypeScript が騒ぎ、その間 scatter ページがビルドできなくなる。
直後に pnpm test:run を回したら 4 ファイル合計 134 件すべて pass。scatterSplit.test.ts の 43 件には、前日に書いた computeFcfYield / computeFcfGrowth / getYAxisDisplay の境界テストが入っていて、ここで null の伝搬を踏ん張ってくれた。
scatter ページの実装中に Codex が刺した 4 ラウンドの指摘
前日(2026-06-15)のセッションで Codex に 4 ラウンド回したログが計画書に残っていて、致命点 7 件を反映した経緯がある。一番効いたのは 2 件。
1 件目: pe フィールドに FCF 利回りを詰める設計は危険、という指摘。ScatterPoint の pe を再利用しただけだと、scatter.vue 側で fmtPe(p.pe) のような PER 前提の固定関数が走り、「FCF 利回りを 12.5x と PER 表記で出す嘘グラフ」が生まれる。対策として MeasureBasis = 'eps' | 'fcf' 型を新設し、ScatterMetrics / ScatterPoint の両方に measure?: MeasureBasis を持たせ、getYAxisDisplay(measure) で {title, format, direction} を一元化した。scatter.vue 側は measureDisplay.value.title を軸タイトルに、measureDisplay.value.format をツールチップの数値整形に通すだけにした。
2 件目: ADR 銘柄の FCF 利回りの式が逆、という指摘。TSM は 1 ADR = 5 普通株なので、EPS と PER の関係は ADR後PER = price / (eps × ratio) で「掛けてから割る」。FCF Yield は PER の逆数なので ADR後Yield = (fcfps × ratio) / price で「掛けて、分子に置く」になる。先に書いた price / (ntm_fcfps × ratio) は FCF Multiple(倍率)で、利回りではない。TSM のプロット位置が完全に壊れる手前で止まった。
このあと「basis を epsBasis にリネーム」と書きながら一部で basis.value 参照が残っているとか、measureDisplay を computed にしたのに .value で unwrap してない、みたいな細かい指摘も Codex が拾った。4 ラウンド目で「致命点なし」と出てから実装に入った。
ゾーン解釈をどう保つか
EPS 基準のときは「左下=割安成長」「右上=割高停滞」で読む。PER は小さいほど良いので、縦軸は下に向かって良くなる。
FCF 基準だと利回りは大きいほど良いので、何も考えずに普通に描くと「左上=割安成長」「右下=割高停滞」に裏返る。ゾーンの読みが揺れると、頭の中で毎回反転計算をやることになる。対策として YAxisDisplay.direction を 'higher-is-better' / 'lower-is-better' で持たせ、scatter.vue 側で y 座標計算を反転する Multiple 表記 に倒した(最終的に FCF Multiple = 1/Yield 表示に切り替え、縦軸反転トリックは不要になった)。これで両基準とも「下に行くほど良い」が保たれる。
ガイド記事(/beat-monitoring-scatter-guide)に「FCF 基準で見る」セクションを追加し、Yartseva 2025 へのリンクと「Multiple 表示にした理由」3 点を残しておいた。
Phase 5 の basis リネームで残った警戒
scatter.vue 側で既存の basis ref(Non-GAAP / GAAP 切替用)を epsBasis にリネームしたとき、テンプレートの class binding・aria-label・disclaimer 表示条件まで波及した。Codex は「split computed で basis.value 参照のまま残っている」と刺してきた。grep -n "basis" apps/web/app/pages/beat-monitoring/scatter.vue で残存ゼロを確認してから commit した。リファクタで変数名を変えるとき、SFC のテンプレート側を全部追えてない事故は何度もやっているので、grep を 1 回挟むだけで救われる。
コミットの順序
朝の点検で見つけたねじれを 2 コミットに分けた。
5548092dfeat(beat-monitoring): BeatValuation 型に FCF フィールドを追補47add3b6docs(takken): scatter FCF 拡張プランを完了状態に更新
1 本目で型のねじれを直し、2 本目で memo/2026-06-15/scatter-fcf-extension-plan.md の implementation-status を「完了(2026-06-16)」に書き換え、Phase 0 〜 6 すべてのチェックボックスを [x] で埋めた。Codex 4 ラウンドの反映ログ表も計画書に残してある。
学び
- データだけ入って型が抜けるケースは生成スクリプト経由だと普通に起こる。スクリプトのテンプレートを書き換えるコミットと、再実行で実体ファイルを更新するコミットは別になるので、間で型不整合が走る。
tsc --noEmitをプッシュ前に挟むのが正解だが、それでも optional フィールドだとどっち向きにも倒せて気付きにくい。 - 「同じ平面を測り分ける」発想は、軸の計算式を注入できる構造があると 5 行で足りる。
buildScatterSplit(tickers, getMetrics)がメトリクス供給を関数注入で受け取る形になっていたおかげで、fcfMetrics(t)を 1 個書き足すだけで第 2 軸が増えた。最初の設計のときに「将来 GAAP / Non-GAAP も切り替えたい」と思って関数注入にしておいたのが、9 ヶ月後に FCF 軸の追加で効いた。 - Codex は「設計のねじれ」を体系的に拾える。型・フィールドの意味の流用が危険なケース、ADR 比率の式の向きが逆になるケース、リネームで参照漏れが残るケース。1 ラウンドで全部拾えるわけではないが、4 ラウンド回すと致命点が枯れる。「瑣末な点へのクソリプはするな」と最初に釘を刺すと、本質的な指摘だけ残る。
- ゾーン解釈は数値の向きで揺れる。「右下=割安成長」を保ちたいなら、軸の direction を
lower-is-better/higher-is-betterでメタとして持ち、y 座標計算で反転する。FCF Multiple 表示に倒せば反転自体が不要になる、という落としどころも見えた。
明日に残ったタスク
- Normalized FCF(5 年平均 capex 引き)を別計画で切り出す。
memo/2026-06-?/scatter-normalized-fcf.mdで立てる - 3 年 CAGR の横軸オプションを追加する。
buildScatterSplitのgrowth計算を NTM 単発 / 3 年 CAGR で切り替える設計を考える - factor lattice(点サイズ・色・形で 3 ファクター重ね)。small-cap / high-profitability の視覚化を入れたい
- FCF が欠損している 3 銘柄(13 銘柄中)を EDGAR フォールバックで埋める案を検討。XBRL 抽出は別パイプラインなので別計画扱い