[{"data":1,"prerenderedAt":579},["ShallowReactive",2],{"content-/hyperscaler-capex-ocf-section-and-koyfin-bug-fix":3,"all-pages-for-dir":577,"og-image-/hyperscaler-capex-ocf-section-and-koyfin-bug-fix":578},{"id":4,"title":5,"body":6,"category":559,"description":560,"extension":561,"meta":562,"navigation":489,"ogImage":563,"path":564,"project_name":565,"published":566,"publishedAt":567,"seo":568,"stem":569,"tags":570,"todo":563,"unpublished":566,"updatedAt":563,"__hash__":576},"pages/2026-06/2026-06-26/hyperscaler-capex-ocf-section-and-koyfin-bug-fix.md","hyperscaler-capex ページに営業CF セクション追加と Koyfin EAC 取り込みパイプラインの CY ラベルバグ修正",{"type":7,"value":8,"toc":546},"minimark",[9,13,17,24,41,60,63,66,70,78,88,92,102,114,117,128,131,134,144,150,161,165,170,247,262,276,280,294,314,325,381,388,392,407,418,421,425,428,436,447,450,468,471,478,508,511,542],[10,11,12],"p",{},"外部分析チームの Google スプレッドシートを開きながら「ハイパースケーラーの CapEx をまとめたページがあったはず。あれに営業CFも並べたい」と頼んだのが朝の最初の依頼だった。たった1セクション足すだけの軽い作業のつもりだったのに、終わってみれば Koyfin の TSV 取り込みパイプラインの隠れバグを2本踏み抜いて、半日が溶けていた。",[14,15,16],"h2",{"id":16},"ページの構成を思い出す",[10,18,19,23],{},[20,21,22],"code",{},"/memory-makers/hyperscaler-capex"," の中身を Claude Code に追わせて、データの出所を整理させた。",[25,26,27,35],"ul",{},[28,29,30,31,34],"li",{},"年次 CapEx は外部分析チームのスプレッドシート由来（",[20,32,33],{},"hyperscalerCapexAnnual","）",[28,36,37,38,34],{},"四半期 CapEx は Koyfin EAC 由来（",[20,39,40],{},"hyperscalerCapexQuarterly",[10,42,43,44,47,48,51,52,55,56,59],{},"CapEx と同じ Koyfin EAC テーブル（",[20,45,46],{},"eac_quarterly","）にメトリクス ",[20,49,50],{},"Cash-from-Operations"," がそのまま入っているのが分かったので、",[20,53,54],{},"generate-hyperscaler-capex-quarterly.mjs"," をコピーしてメトリクス名だけ差し替えた ",[20,57,58],{},"generate-hyperscaler-ocf-quarterly.mjs"," を生成させた。",[10,61,62],{},"ページ側は CapEx の「四半期テーブル」直後・「年次 CapEx」の前に、同じ構造（個社別小チャート → 全社合計 → 比較テーブル）でセクションを差し込んだ。ついでに「CapEx / 営業CF 比率」テーブルも追加して、設備投資が営業 CF の何 % を食っているかを並べた。",[10,64,65],{},"ここまでは30分で済んだ。崩れたのはこの後の「実データ更新」だった。",[14,67,69],{"id":68},"db-の最新実績が半年止まっていた","DB の最新実績が半年止まっていた",[10,71,72,73,77],{},"スクショで dev サーバー上のページを見ると、最新実績クォーターが ",[74,75,76],"strong",{},"CY2025Q3"," までしか出ていない。MSFT は5月に CY2026Q1（= 3Q FY2026A）の決算を出しているはずなので、画面の数字がちょうど半年遅れている。",[10,79,80,83,84,87],{},[20,81,82],{},"/check-earnings"," の日次更新対象は NVDA / MU / SNDK の3銘柄だけで、ハイパースケーラー7社は ",[74,85,86],{},"手動で Koyfin EAC を引いて取り込む","運用だった、と Claude Code が思い出させてくれた。",[14,89,91],{"id":90},"chrome-拡張の-postmessage-ブリッジで7社一気にdl","Chrome 拡張の postMessage ブリッジで7社一気にDL",[10,93,94,97,98,101],{},[20,95,96],{},"chrome-extension-kofyin"," には Phase 2 で実装した postMessage ブリッジ（",[20,99,100],{},"window.__kofyin.runSingleDownload(...)","）が既にある。Chrome DevTools MCP 経由で MSFT タブを操り、",[103,104,105,108],"ol",{},[28,106,107],{},"ticker ごとに EAC ページの URL を解決",[28,109,110,113],{},[20,111,112],{},"runSingleDownload"," を呼んで TSV をダウンロード",[10,115,116],{},"を Claude Code に順次回させた。1社あたり約30秒。MSFT → GOOGL → AMZN → META → AAPL → ORCL → CRWV の7社で3分強。並列にしたい欲を抑えて直列で回したのは、Koyfin 側の rate limit を踏みたくなかったから。",[10,118,119,120,123,124,127],{},"DL したファイルが「ダウンロード」「ダウンロード (1)」…「ダウンロード (8)」という汎用名で保存されていて、",[20,121,122],{},"subfolder/filename"," 指定が効いていない様子だった。中身の TSV は正しく ticker と timestamp を持っていたので、リネーマ経由で ",[20,125,126],{},"actual-and-consensus/2026-06-26/"," に正規ファイル名で並べ直してから SQLite に流し込んだ。",[10,129,130],{},"「TSV さえ正しければ取り込みは通るはず」とこの時は思っていた。",[14,132,133],{"id":133},"取り込みが通ったのに数字がズレる",[10,135,136,139,140,143],{},[20,137,138],{},"eac_tsv_to_sqlite.py"," が「7社インポート成功」と出して、",[20,141,142],{},"apps/web/data/koyfin.db"," に同期したあと、念のため MSFT の最新行を直接 SELECT した。出てきた結果が、",[145,146,147],"blockquote",{},[10,148,149],{},"1Q FY2026A = Mar-31-2026",[10,151,152,153,156,157,160],{},"Koyfin の画像と照らすと、画像側では同じ ",[20,154,155],{},"1Q FY2026A"," が ",[74,158,159],{},"Sep-30-2025","。6ヶ月ズレている。画面の数字がうまそうに見えても、period_ending の月日が違うので意味が反対になっている。**取り込みが「通った」のに「正しくない」**というのが一番たちが悪い。",[14,162,164],{"id":163},"バグ1-cycalendar-yearラベルを弾いていた","バグ1: CY（Calendar Year）ラベルを弾いていた",[10,166,167,169],{},[20,168,138],{}," を grep で読んでもらうと、200行目あたりで",[171,172,177],"pre",{"className":173,"code":174,"language":175,"meta":176,"style":176},"language-python shiki shiki-themes vitesse-light vitesse-light","if header_row[0] not in (\"Fiscal Years\", \"Fiscal Quarters\"):\n    return  # スキップ\n","python","",[20,178,179,237],{"__ignoreMap":176},[180,181,184,188,192,196,200,203,207,210,213,217,221,223,226,229,232,234],"span",{"class":182,"line":183},"line",1,[180,185,187],{"class":186},"sHkkW","if",[180,189,191],{"class":190},"sG7-3"," header_row",[180,193,195],{"class":194},"shFtX","[",[180,197,199],{"class":198},"sM54T","0",[180,201,202],{"class":194},"]",[180,204,206],{"class":205},"stQ0i"," not",[180,208,209],{"class":205}," in",[180,211,212],{"class":194}," (",[180,214,216],{"class":215},"sMJiu","\"",[180,218,220],{"class":219},"sdGka","Fiscal Years",[180,222,216],{"class":215},[180,224,225],{"class":194},",",[180,227,228],{"class":215}," \"",[180,230,231],{"class":219},"Fiscal Quarters",[180,233,216],{"class":215},[180,235,236],{"class":194},"):\n",[180,238,240,243],{"class":182,"line":239},2,[180,241,242],{"class":186},"    return",[180,244,246],{"class":245},"sxvE3","  # スキップ\n",[10,248,249,250,253,254,257,258,261],{},"という早期 return が刺さっていた。今回 Koyfin から落ちてきた TSV のヘッダは ",[20,251,252],{},"Calendar Years"," / ",[20,255,256],{},"Calendar Quarters","。条件にヒットせず ",[74,259,260],{},"全行スキップ"," されていた。「7社インポート成功」のログは出るのに DB は1行も更新されない、という嫌な状態。",[10,263,264,265,268,269,156,272,275],{},"修正は ",[20,266,267],{},"(\"Fiscal Years\", \"Fiscal Quarters\", \"Calendar Years\", \"Calendar Quarters\")"," の4本立てに広げるだけ。",[20,270,271],{},"fetched_at",[20,273,274],{},"2025-12-14"," のまま固まっていた既存6社のレコードを一旦削除してから、再インポートさせた。",[14,277,279],{"id":278},"バグ2-period_ending-が全社共有の設計だった","バグ2: period_ending が「全社共有」の設計だった",[10,281,282,283,286,287,156,290,293],{},"これでも MSFT の ",[20,284,285],{},"period_ending"," は妙な日付のまま。さらに掘ると ",[20,288,289],{},"get_or_create_eac_period(period_label, ...)",[74,291,292],{},"ticker を引数に取らず、period_label だけで periods 行を共有していた","。",[10,295,296,297,299,300,303,304,306,307,309,310,313],{},"具体的には、ORCL は5月決算なので ",[20,298,155],{}," の period_ending は ",[20,301,302],{},"Aug-31-2025","。それが先に DB に入っていたために、後から取り込んだ MSFT / GOOGL / AMZN の ",[20,305,155],{}," も同じ ",[20,308,302],{}," を参照してしまう。Koyfin の ",[20,311,312],{},"1Q FYxxxxA"," というラベルは会計年度を全社で共有してはいけない概念なのに、テーブル設計が共有を許してしまっていた。",[10,315,316,317,320,321,324],{},"「設計を直す」のが正攻法だが、ratio table が壊れたまま明日に持ち越したくなかったので、最短のワークアラウンドとして CY ラベル（",[20,318,319],{},"1Q CY2026A"," 等）の period_ending を ",[74,322,323],{},"カレンダー末日に一括 UPDATE"," した。CY ラベルは定義上 calendar quarter なので、ticker 横断で同じ日付になっても問題が起きない。",[171,326,330],{"className":327,"code":328,"language":329,"meta":176,"style":176},"language-sql shiki shiki-themes vitesse-light vitesse-light","UPDATE eac_periods\n   SET period_ending = '2026-03-31'\n WHERE period_label = '1Q CY2026A';\n","sql",[20,331,332,340,360],{"__ignoreMap":176},[180,333,334,337],{"class":182,"line":183},[180,335,336],{"class":186},"UPDATE",[180,338,339],{"class":190}," eac_periods\n",[180,341,342,345,348,351,354,357],{"class":182,"line":239},[180,343,344],{"class":186},"   SET",[180,346,347],{"class":190}," period_ending ",[180,349,350],{"class":205},"=",[180,352,353],{"class":215}," '",[180,355,356],{"class":219},"2026-03-31",[180,358,359],{"class":215},"'\n",[180,361,363,366,369,371,373,375,378],{"class":182,"line":362},3,[180,364,365],{"class":186}," WHERE",[180,367,368],{"class":190}," period_label ",[180,370,350],{"class":205},[180,372,353],{"class":215},[180,374,319],{"class":219},[180,376,377],{"class":215},"'",[180,379,380],{"class":190},";\n",[10,382,383,384,387],{},"これで MSFT の最新実績が ",[20,385,386],{},"1Q CY2026A = Mar-31-2026 = 46,679"," に揃って、Koyfin の画像と数字も日付も完全一致した。",[14,389,391],{"id":390},"dev-サーバーの-hmr-が取りこぼした","dev サーバーの HMR が取りこぼした",[10,393,394,395,398,399,402,403,406],{},"生成スクリプトを再実行して TS データを吐かせたあと、ページをリロードしても「最新実績: 2025Q3」のまま動かない。データ TS を ",[20,396,397],{},"cat"," で直接確認すると ",[20,400,401],{},"2026Q2"," まで入っている。dev サーバーが ",[20,404,405],{},"app/data/memory-makers/"," 配下のファイル変更を拾い切れていない様子だった。",[10,408,409,410,413,414,417],{},"Windows + pnpm dev でたまにある現象なので、3000番のプロセスだけを ",[20,411,412],{},"Get-NetTCPConnection -LocalPort 3000"," で特定して落とし、",[20,415,416],{},"pnpm dev"," を立て直した。リロード後、「最新実績: 2026Q2」表示で MSFT の46,679も乗った。OCFセクションも個社別チャート・全社合計・比率テーブルが全部描画された。",[10,419,420],{},"CapEx / 営業CF 比率テーブルは2025Q1〜Q3 の3行表示。AAPL と CRWV の Freeプラン制限で2025Q4以降が全社揃わないので絞り込みが効いて3行に減っているのが原因で、ロジックは正しい。",[14,422,424],{"id":423},"おまけ-簿記3級ページの-html-仕様違反","おまけ: 簿記3級ページの HTML 仕様違反",[10,426,427],{},"作業中、dev サーバーのコンソールに",[171,429,434],{"className":430,"code":432,"language":433},[431],"language-text","\u003Ctable> 直下に \u003Ctr> があります（\u003Ctbody> で包んでください）\n","text",[20,435,432],{"__ignoreMap":176},[10,437,438,439,442,443,446],{},"という警告が ",[20,440,441],{},"accounting-app-play-gimmicks.vue"," から流れていた。ハイパースケーラーの作業とは無関係だが、目に入った警告は今のうちに潰しておきたいタチなので、該当箇所2つを ",[20,444,445],{},"\u003Ctbody>"," で包んで終わらせた。「ハイパースケーラーの確認のためにそのページ開いてくれてたんですよね？」と聞かれて慌てて軌道修正したが、警告ゼロにはなった。",[14,448,449],{"id":449},"学び",[25,451,452,459,465],{},[28,453,454,455,458],{},"「取り込みが通った」ログを信用しない。",[74,456,457],{},"画面の数字と外部の正本（今回は Koyfin の画像）を1組だけでも目視で突き合わせる","と、period_ending のズレのような「成功扱いのバグ」を1分で踏める",[28,460,461,464],{},[20,462,463],{},"eac_periods"," のような「外部キーで共有される寸法表」は、共有していい単位（CY ラベル）と共有してはいけない単位（FY ラベル）を、テーブル設計の段階で分けるのが正しい。今回は CY ラベルの一括 UPDATE で逃げたが、FY ラベル側を ticker ごとに分離する改修が宿題として残っている",[28,466,467],{},"Chrome 拡張の postMessage ブリッジは「Chrome DevTools MCP からポップアップをクリックできない」問題の優れた回避策。7社×30秒で3分強で済むので、月次運用ならこれで十分",[14,469,470],{"id":470},"明日に残したこと",[10,472,473,474,477],{},"進捗は ",[20,475,476],{},"memo/2026-06-26/hyperscaler-capex-ocf-progress.md"," に置いた。明日やりたいのは、",[25,479,482,496,502],{"className":480},[481],"contains-task-list",[28,483,486,491,492,495],{"className":484},[485],"task-list-item",[487,488],"input",{"disabled":489,"type":490},true,"checkbox"," ",[20,493,494],{},"get_or_create_eac_period"," を ticker 単位に分離する設計改修（FYラベル側）",[28,497,499,501],{"className":498},[485],[487,500],{"disabled":489,"type":490}," AAPL / CRWV の Freeプラン制限で歯抜けになる列の扱い方（直近4Qのみ表示にする等）",[28,503,505,507],{"className":504},[485],[487,506],{"disabled":489,"type":490}," CapEx / 営業CF 比率テーブルの空欄行を「N/A」で埋めて行数を保つ",[14,509,510],{"id":510},"関連ファイル",[25,512,513,519,525,531,537],{},[28,514,515,516],{},"ページ: ",[20,517,518],{},"apps/web/app/pages/memory-makers/hyperscaler-capex.vue",[28,520,521,522],{},"CapEx 四半期スクリプト（元ネタ）: ",[20,523,524],{},"apps/web/scripts/generate-hyperscaler-capex-quarterly.mjs",[28,526,527,528],{},"今回作った営業CF スクリプト: ",[20,529,530],{},"apps/web/scripts/generate-hyperscaler-ocf-quarterly.mjs",[28,532,533,534],{},"取り込みバグ本体: ",[20,535,536],{},"chrome-extension-kofyin/scripts/eac_tsv_to_sqlite.py",[28,538,539,540],{},"進捗メモ: ",[20,541,476],{},[543,544,545],"style",{},"html pre.shiki code .sHkkW, html code.shiki .sHkkW{--shiki-default:#1E754F;--shiki-dark:#1E754F}html pre.shiki code .sG7-3, html code.shiki .sG7-3{--shiki-default:#393A34;--shiki-dark:#393A34}html pre.shiki code .shFtX, html code.shiki .shFtX{--shiki-default:#999999;--shiki-dark:#999999}html pre.shiki code .sM54T, html code.shiki .sM54T{--shiki-default:#2F798A;--shiki-dark:#2F798A}html pre.shiki code .stQ0i, html code.shiki .stQ0i{--shiki-default:#AB5959;--shiki-dark:#AB5959}html pre.shiki code .sMJiu, html code.shiki .sMJiu{--shiki-default:#B5695977;--shiki-dark:#B5695977}html pre.shiki code .sdGka, html code.shiki .sdGka{--shiki-default:#B56959;--shiki-dark:#B56959}html pre.shiki code .sxvE3, html code.shiki .sxvE3{--shiki-default:#A0ADA0;--shiki-dark:#A0ADA0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":176,"searchDepth":239,"depth":239,"links":547},[548,549,550,551,552,553,554,555,556,557,558],{"id":16,"depth":239,"text":16},{"id":68,"depth":239,"text":69},{"id":90,"depth":239,"text":91},{"id":133,"depth":239,"text":133},{"id":163,"depth":239,"text":164},{"id":278,"depth":239,"text":279},{"id":390,"depth":239,"text":391},{"id":423,"depth":239,"text":424},{"id":449,"depth":239,"text":449},{"id":470,"depth":239,"text":470},{"id":510,"depth":239,"text":510},"dev","ハイパースケーラー設備投資ページに営業キャッシュフローを並べる過程で、Koyfin EAC 取り込みスクリプトが Calendar Year ラベルを弾く致命的バグと、period_ending が他社で上書きされる設計バグを画面の数字で踏み抜いた記録。","md",{},null,"/hyperscaler-capex-ocf-section-and-koyfin-bug-fix","financial-data",false,"2026-06-26T00:00:00.000Z",{"title":5,"description":560},"2026-06/2026-06-26/hyperscaler-capex-ocf-section-and-koyfin-bug-fix",[571,572,573,574,575],"memory-makers","ハイパースケーラー","Koyfin","Cash from Operations","MSFT","qjRrQ-CYtVczLCyGjiTWDXVMNHaN4H1tO4Mb-lPeBAs",[],"https://log.eurekapu.com/og/blog/hyperscaler-capex-ocf-section-and-koyfin-bug-fix.png?v=2026-06-26T00%3A00%3A00.000Z&title=hyperscaler-capex%20%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AB%E5%96%B6%E6%A5%ADCF%20%E3%82%BB%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E8%BF%BD%E5%8A%A0%E3%81%A8%20Koyfin%20EAC%20%E5%8F%96%E3%82%8A%E8%BE%BC%E3%81%BF%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3%E3%81%AE%20CY%20%E3%83%A9%E3%83%99%E3%83%AB%E3%83%90%E3%82%B0%E4%BF%AE%E6%AD%A3&author=Kei%20Komatsu&sig=1d1b67c64021719a",1782528860886]