朝の出発点: 拡張機能 + DL ベースだったはずの設計
朝、机に座って昨日の積み残しメモを開いた。「決算データプラットフォーム A の Actuals & Consensus データを Turso に蓄積するパイプラインの最終段」が残っていた。前日までの計画では、自作の Chrome 拡張機能のコピー機能を流用して、プラットフォーム上の該当ページから TSV をダウンロードさせ、それを Python で Turso に流す構成だった。
設計を立て直す前に、ユーザーから「browser-to-api というスキルがオープンソースで公開されているけれど、丸ごとダウンロードしないで必要なロジックだけ分析してこっちで実装してほしい」と注文が入った。Codex に計画レビューを依頼すると、致命的指摘が 3 件返ってきた。そのうち 1 件が朝のうちに刺さることになる: 「SNDK のプラットフォーム内部 ID は、他銘柄と同じ命名プレフィックスではない可能性がある」。
Phase 1-A: 銘柄検索の通信経路を Chrome DevTools MCP で当てに行く
最初の山はプラットフォーム側の銘柄識別子(以下 KID)の解決だった。NVDA は URL から拾えていたが、MU と SNDK は URL を踏まないと分からない。手作業で 1 銘柄ずつ URL を踏むのは運用に乗らないので、プラットフォームのフロントが叩いているはずの銘柄検索エンドポイントを当てに行くことにした。
Chrome DevTools MCP でログイン済みのプラットフォームタブを掴み、検索バーをクリックしてポップアップを出させ、"MU" と入力した瞬間のネットワークリクエストを list_network_requests で拾った。一発で見つかった。
リクエスト body と JSON レスポンスを get_network_request で取得して、3 銘柄分の KID を確定させた。f6- で始まる SNDK の KID を見た瞬間、Codex の指摘 B が机の上で実証された。プランの「他銘柄と同じプレフィックスで URL を組み立てる」ロジックを採用していたら、SNDK だけ静かに 404 を返して気付かないところだった。
Phase 1-B / 2: 拡張機能で DL するところまでは動いた
午前中の前半は予定どおり、拡張機能の content.js 末尾に getTickerId と単一銘柄ダウンロードハンドラ、該当ページへのボタン inject を追加していった。Content Script Isolation の壁に当たって isolated world のオブジェクトが MCP の evaluate_script から見えない問題には postMessage ブリッジを噛ませて回避した。
ボタンを inject して NVDA_EAC_20260519.tsv をダウンロードさせる E2E まで通った。chrome.downloads.onChanged の complete を待って resolve するところまで動いた。歓声をあげかけたところで Downloads フォルダを覗くと、ファイル名が ダウンロード という固定文字列になっていた。
Workspace 管理ブラウザの DLP ポリシーが、ファイル名を強制リネームしていた。中身はちゃんと NVDA のデータだったが、名前がぜんぶ「ダウンロード」になるので、複数銘柄を回したら上書きで死ぬ。
設計の捨て直し 1: 「Claude Code 自身に DL ファイルを見せればいい」
ここでユーザーから方向転換の一言が飛んできた。
いや、これはスラッシュコマンドでやるわけじゃないですか。スクリプトでやるんじゃなくて、Claude Code 自身にそのダウンロードファイルを見てもらうフローに変えればいいんじゃないですか。
確かにファイル名さえどうにかなれば、Python は無改修で動く。Claude Code が DL 前後でディレクトリ差分を取り、新規ファイルの中身を読んでティッカーを判定し、正しい名前に置き直してから Python ランナーに渡す、という構成に書き換えた。check_earnings.py に --mcp-fetched フラグを足し、check-earnings.md も Claude Code 向けの手順書に直した。
E2E を回すと、パイプラインは通ったが取り込み件数が 0 件で返ってきた。原因は別の場所にあった。期間ラベルのパース関数が FY ... 表記しか想定しておらず、ページ表示が Calendar Years に切り替わっていたヘッダを全部スキップしていた。issue にメモして朝のセッションを区切った。
設計の捨て直し 2: 欲しいのは Actuals & Consensus じゃなくて Estimates Overview だった
午前後半、ユーザーからプラットフォーム画面のスクリーンショットが 3 枚届いた。
このアナリスト Estimate の中の、Estimates Overview の中の、Sales と EBITDA と EBIT と EPS と EPS GAAP、これを毎日取得したいわけなんですよ。
実装していたのは Actuals & Consensus ページだった。本当に欲しいのは Estimates Overview ページの Earnings Matrix - Reported & Estimates セクションで、5 メトリクス × FY 2026-2029 × (1Q-4Q + Year + Growth) のマトリクスだった。手で書いた DOM スクレイパーで Earnings Matrix を読むところまで作ってみたが、ふと「さっき銘柄検索の通信経路を当てたのと同じ手で、Estimates 側のデータ取得経路も特定できるんじゃないか」と気付いた。
設計の捨て直し 3: 拡張機能を外して、データ取得経路を直接呼ぶ
Chrome DevTools MCP で再びプラットフォームタブを掴み、Estimates Overview ページをリロードしながら list_network_requests を流した。データキー指定型の汎用 RPC エンドポイントが複数回叩かれていて、そのうち 1 リクエストのレスポンスが Earnings Matrix の数字とぴったり一致した。リクエスト body の keys で欲しいデータを宣言する形になっていた。
Sales / EPS / EPS GAAP の fy0-fy3 + fq1-fq11 を指定してみたら、Free アカウントで全件返ってきた。EBIT / EBITDA だけは Basic 必須で 403 が返ったが、優先度を Sales / EPS / EPS GAAP に絞ればこの 3 メトリクスは全部取れる。拡張機能を外して、データ取得経路を直接呼ぶ設計に丸ごと差し替えた。
朝に書いた content.js の追加コードも、postMessage ブリッジも、--mcp-fetched ハンドラも、ここでぜんぶ役目を終えた。
認証の壁を MCP の evaluate_script でかわす
Python から直接通信させる案は、auth_token が Bash transcript に残るので許可を弾かれた。理にかなった判断だった。代わりに MCP の evaluate_script でログイン済みドメイン上から fetch を発火させ、JSON レスポンスを %TEMP% 配下のファイルに保存させて、そのファイルだけ Python に stdin で流し込む構成にした。auth_token は Chrome 側で完結し、Python は JSON しか触らない。
// MCP evaluate_script 内で実行
const res = await fetch(ENDPOINT, {
method: 'POST',
headers: { 'content-type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ keys: [...] })
})
// レスポンスを TEMP に保存
NVDA の取り込みが通ったあと、内部スキーマのキー名が前方一致で別のメトリクスに誤判定されるバグが出た。長い prefix 優先に直して直った。
午後: MU / SNDK / 冪等性 / コマンド書き換え
午後は KID を差し替えて MU と SNDK を順に流した。両方とも 1 発で通った。
| 銘柄 | periods | estimates |
|---|---|---|
| NVDA | 18 | 60 |
| MU | 18 | 47 |
| SNDK | 18 | 47 |
念のため NVDA を 2 回流して、estimates テーブルの件数が 60 のままで増えないことを確認した。INSERT ... ON CONFLICT の冪等性が効いている。
そのあと /check-earnings.md を新しい経路に書き換えた。朝の段階では「拡張機能のボタンを押す → DL を待つ → ファイルをリネーム → Python」の手順書だったのを、「タブを選んで evaluate_script で 1 銘柄ずつ JSON を取得 → Python に流す」の 3 ステップに圧縮した。
C-3: ガイダンスも同じ Turso に押し込む
別件で動いている nvidia-guidance-watcher の notify.py に Turso 書き込み機能を足した。SEC EDGAR から拾ったガイダンス(売上・粗利率レンジ・OpEx など)を consensus_estimates と同じ DB の guidance テーブルに UPSERT する形にして、data/{TICKER}_latest.txt から 3 銘柄 × 5 行を入れた。これも冪等性を確認した。
C-5 / C-6: Worker とフロントは作ったが本番デプロイは保留
午後の後半、Cloudflare Worker API のスケルトンとフロントの書き換えまで進めた。Worker は wrangler の dry-run でコンパイルが通り、/api/health と SPA 配信は 200 で返るところまで確認した。ただし .dev.vars に Turso の認証情報を入れる工程はユーザー側の手作業が必要で、ここで方針判断が入った。
Cloudflare のデプロイはとりあえず置いといて大丈夫です。スラッシュコマンドでその日のデータを取って、決算があれば見に行くフローができていればそれで十分です。
データパイプライン本体は /check-earnings 1 発で稼働するところまで来ているので、Worker のローカル動作検証と本番デプロイは保留にして締めることにした。Stop hook が「全機能完成」を要求して何度か再開を促してきたが、ユーザーの意思決定が明示されたあとは判断を曲げずに保留扱いを維持した。
振り返り: 朝の設計を 3 回捨て直した
今日の本当の作業は実装ではなく、朝に書いた設計を 3 回捨て直すことだった。
- 拡張機能 + DL を完成させた直後に、Workspace のファイル名強制リネームで足元が崩れた
- 「Claude Code に DL ファイルを見せる」案に乗り換えた直後に、見るべきページが Actuals & Consensus じゃなく Estimates Overview だと判明した
- DOM スクレイパーを書き始めた瞬間に、銘柄検索の経路を特定したのと同じ手で データ取得側も同じパターンで掴めると気付いて、拡張機能ごと捨てた
Codex の事前指摘「SNDK は他銘柄と同じプレフィックスじゃない可能性がある」が朝のうちに実証されたのも、設計を 1 回捨てて済んだ分が積み上がった結果だった。
税理士・会計士視点での応用
顧問先の財務データを毎日 Turso に貯める構成は、税務にもそのまま持ち込める。月次推移を国税庁の e-Stat や、顧問先のクラウド会計の公開 API から日次で吸い上げて 1 つの DB に並べれば、決算月前の数字の動きをスラッシュコマンド 1 発で横串に確認できる。「今月の試算表を出してほしい」と毎月電話で頼まれる作業のうち、データ取得の部分はこれで自動化の射程に入る。