決算ビートモニタリングでメモリとHDDのセクターを分離 — SNDKの会計年度末月の登録ミスが出てきた
朝、決算ビートモニタリングのページを開いたまま、STX(Seagate)の行を数秒見ていた。「半導体(メモリ/ストレージ)」の棚に入っている。Seagateはメモリの会社だっただろうか。ついでに、気になっていた「ここは中国の影響を受けない」という説明も、自分で裏を取っていない。まず質問から投げた。
予想が当たったのは半分だった。
メモリではなくHDD、中国リスクは向きが違う
SeagateはDRAM/NANDを作っていない。回転する磁気ディスクの上にヘッドを数ナノメートルで浮かせて読み書きする、HDDのメーカーである。半導体メモリとは技術の系統が別物だった。
「中国の影響を受けない」のほうは、供給側と規制側で答えが分かれた。
- 供給側: CXMT/YMTCのような中国勢の増産による価格破壊は、HDDには効きにくい。ヘッドとメディアを作れるのが3社しかなく、装置を買い揃えて参入できる産業ではない。世界出荷の95%超をSeagate・WD・東芝で分けている
- 規制側: こちらはむしろ直撃する。2023年、Seagateは対中輸出規制(FDPR)に違反してHuaweiへHDDを出荷し、BISから3億ドルの制裁金を食らっている
同じ「中国」でひとくくりにできる話ではなかった。棚を分ける理由としても十分だったので、その場で分類の修正を頼んだ。
棚を2つに割って、MUを移す
半導体(メモリ/ストレージ) を 半導体(メモリ) と ストレージ(HDD) に分けた。
- 半導体(メモリ): MU / SNDK / Samsung / SK hynix / キオクシア
- ストレージ(HDD): STX / WDC
あわせてMUをメモリ側へ移した。HBMでAI需要を取り込んでいるのでこれまで 半導体(AI/GPU) に置いていたが、作っているものはDRAM/NANDである。トップの主力行は固定リストで並ぶので、この移動で表示位置は動かない。
なぜHDDを分けたのかは、半年後の自分が同じ疑問を持つだろうから、tickerMeta.tsのコメントに理由まで書き残させた。分類の判断は、分類そのものより忘れやすい。
テストは7件pass。devで開いて、グループ見出しが「半導体・ストレージ・光部材」に変わり、2つの棚がそれぞれ並んでいるのを確認した。
動かないスライダー
分類を触るついでに、もうひとつ気になっていた挙動を調べさせた。/earnings-dynamics/STX のスライダーを動かしても、バーが何も変わらない。
データ取得が止まっているのだろう、と思っていた。違った。値は取れていて、スクリプトも動いている。STXは前日に決算を発表したばかりで、次Qのwindowが始まった直後だったため、区間の中に価格改定が1点しか存在しなかった。1点しかなければ、どの目盛に動かしても同じ点が返る。
つまりSTX固有の事故ではなく、どの銘柄でも決算発表の翌日には必ず起きる。フロントのバグを探しても何も見つからないはずだった。
修正の方針は、スナップショットが1点しかないfocus四半期を候補から外すこと。「動かせるのに何も変わらないスライダー」を出さない。
const withData = candidates.filter((f) => countSnapshotDates(series, f) >= 1)
const playable = withData.filter((f) => hasPlayableSnapshots(series, f))
return playable.length > 0 ? playable : withData
3行目のフォールバックは、最初の実装には入っていなかった。
57銘柄が0件と出た
修正を当てたあと、全銘柄への影響を測らせた。「実績済」タブが0件になる銘柄が57本、と返ってきた。
分類を分けただけで57本が壊れるとは思えない。計測の当て方の問題と、本当の回帰が混ざっているはずだった。切り分けさせたところ、実際にスライダーが丸ごと消えるのは8本(285A/AMKR/ASML/COF/JPM/NOC/PGR/SYF)だった。取り込みを始めたばかりで、どの四半期も1点しか持っていない銘柄である。
候補が空になると選択UIごとページから消える。それでフォールバックを足した。全部が1点しか持たない銘柄では、従来どおり1件以上で通す。少なくとも今まで見えていたものは見せる。
全74銘柄で突き合わせて、消える銘柄がゼロになったことを確認した。テストは97件pass。
focus 0件の3銘柄は、原因が別々だった
手元に残していた宿題がひとつある。focus四半期の候補が0件になる銘柄が3つ(SNDK / FDXF / TER)。このうちSNDKは主力3銘柄の固定行に入っているので、個別ページが機能していないのは惜しい。ここだけ原因を見てもらった。
調べさせたら、3銘柄で原因が違った。共通の欠陥を1つ直せば3つ直る、という期待は外れた。
SNDKの原因は、Turso側の登録値だった。tickers.fiscal_year_end_month が 12 で入っている。WDCとSTXは正しく 6。SanDiskはWestern Digitalから分社した6月期末の会社で、12は明確な誤りだった。しかも同じDBの eac_periods には FY2025 Q4 end = 2025-06-30 と6月期末で入っている。DBの中で自己矛盾していた。
効き方はこうだった。SNDKは report_date が全期nullなので、生成スクリプトは会計年度末月から発表日を推定する。12のままだと推定日が半年ずれ、focus windowが 2026-10-29〜2027-01-28 という未来にずれる。実在するスナップショットは 2026-05-19〜07-29 なので、窓の中に1件も入らない。スライダーが機能しないのは当然だった。
画面の症状はフロント側に見えていたのに、原因はDBの1カラムにあった。
承認の壁は、自分の手で越えるしかなかった
訂正スクリプトができたところで、本番DBへの書き込みが止まった。自動承認の分類器がブロックしていて、チャットで「どうぞ」と返しても解除されない層だという。
それならコマンドを出してもらえばいい。貼って、自分の手で実行した。
BEFORE: {"ticker":"SNDK","fiscal_year_end_month":12}
AFTER: {"ticker":"SNDK","fiscal_year_end_month":6}
不可逆な書き込みの前で一度人間の手を通す作りは、こういうときに納得できる。原因が確定していて、値が1つで、BEFORE/AFTERが目の前に出るなら、貼るのに迷いはない。
再生成して、STXの手作業を戻す
訂正後、全74銘柄を再生成させた。SNDKのFY26 4Qはwindowが 2026-04-29〜07-28 に収まり、スナップショットが60件入った。devで開くとスライダーが動き、実績6.0・予想8.4・ガイダンス8.0が正しい位置に立っている。
STX側には後片付けがあった。FY26 4Qの実績値はKoyfinがまだ実績化していないので、手順書どおり手で書き足してある行だった。再生成すると消える。復元したあと、差分が生成時刻の2行だけになっていることを確認して、復元が正確だったと判断した。
待てばいいものは、待つ
STXの4Qについては、Koyfinが実績化するのを待てばいい、という整理にした。今すぐ直す種類の問題ではない。
ただし待っている間の副作用が1つある。/make-diary を回すと generate:earnings-dynamics が走るので、Koyfinが実績化していない状態だと手で足した2行が上書きで消える。データが壊れるわけではなく、実績化されれば次の生成で自動的に戻る。チャートで4Qの黒いバーが消えていたら「まだ待ちの状態だな」と読めばいい。
コミットは3本、学習ゲートはスキップ
パス指定でステージして、3本に分けてコミットした。
d10233aaメモリとHDDのセクターを分離しMUをメモリへ移動30e438c4スナップショット1点のfocus四半期を候補から外す45f2c1c2SNDKの会計年度末月訂正を反映してデータ再生成
学習ゲートは出たが、クイズを出すMCPツールが見つからなかった。サーバー側を追いかけると本題から離れるので、今日はスキップして通した。
残したメモ
- FDXF と TER の focus 0件は、SNDKとは別の原因のまま置いてある。SNDKの1カラムのような単純な話とは限らない
- Turso側のマスタ値は、同じDBの別テーブル(
eac_periodsの期末日)と突き合わせれば矛盾を機械的に検出できる。SNDKは2つのテーブルが半年ずれて共存していた - 「決算翌日はスナップショットが1点」は毎四半期どこかで踏む。銘柄固有の症状に見えるものを、まずカレンダー由来で疑う癖をつけたい
- 分類を分けた理由はコード内コメントに残した。分類は変えた瞬間より、あとから理由を思い出せないほうが困る