財務省貿易統計で日本のNAND輸出単価を再現し、統計ページにする

開発mdx-playground

Xのタイムラインに「日本のNAND輸出単価 月次推移グラフ」という図が流れてきた。 添えられた説明によると、2022年から2025年までおおむね1,000〜2,500円のボックス圏で動いていた線が、2026年4月以降にその枠を抜け、7月は7,631.5円まで跳ねているという。

跳ねたこと自体より先に気になったのは、この数字がどこから出ているのかだった。 月次で誰でも取れる統計なら、自分のサイトに常設したい。 出所が分からないうちは載せられない。 だから最初に投げたのは、チャートを描く指示ではなく、出所を特定する指示だった。

図の数字が手元で再現できるか

Claude Code に統計の当たりを付けさせたら、財務省貿易統計の統計品別表に行き着いた。 9桁の統計品目番号で品目が細分されていて、NAND(8542.32-921)が単独で取れる。

2026年7月分は、金額が185,383,600千円、数量が24,291,760個だった。 割ると7,631.5円になった。 Xの図と小数第1位まで一致した。

ここで作ることを決めた。 同じ数字が自分の手元から出るなら、あとは取得と表示をつなぐだけになる。

公表サイクルを確認させたところ、そこに落とし穴があった。 月次の「速報」は翌月20日頃に出るが、9桁の品目別が入るのは翌月末に出る「確報」のほうである。 速報の日に取りにいっても、新しい月は1行も増えない。 更新の当番日は確報側に合わせないと、毎月「変更なし」だけを見て終わることになる。 公表予定日の正本は税関の公表予定カレンダーの「輸出確報」列に載っている。

DRAMと完成品の数字が小さいのが引っかかった

同じ表にはDRAM(8542.32-911)と、SSDやUSBメモリのような完成品(8523.51-000)も並んでいる。 数字を眺めていると、どちらも金額が小さい。 日本から出ていくメモリは、完成品より前工程寄りのものが中心という構図に見える。

構図を確かめたくて、輸入側も取らせて見比べた。 輸入は品目の細分が輸出と違うので、まず品名を突き合わせるところから始めてもらった。 比較は構図の確認だけで終わらせ、ページに載せるのは輸出側に絞った。

報告と実物のずれ

ここで一度、手を止めさせた。 「今回作ってくれた記事ページはどこですか。フルパス教えて」と聞いた。

返ってきたのは「まだ1つもファイルを作っていない」という答えだった。 前の応答には「実装に入ります」と書いてあった。 それでもディスクには何も無い。

調査の報告が具体的だと、実装まで進んだ気になる。 実際に増えていたのはコンテキストだけで、ファイルは0件だった。 以降は区切りごとにフルパスを聞くようにした。 存在しないパスは、聞けばその場で分かる。

SSOTの設計とパーサ

取得したデータは、ページが読む1本のTypeScriptファイル(SSOT)にまとめることにした。 収録する期間は2022年1月以降に区切った。 HS2022の発効でこの年から品目細分がそろうため、それより前は同じ系列として並べられない。

収録したのは10品目。 ページに出すのはNANDとベアチップ、DRAM、完成品の4つで、残りはSSOTに持たせておく。 系列を足したくなったときにデータを取り直さずに済む。

単価は金額を数量で割って出す。 数量の単位が「個」でない品目は単価を null にした。 8542.32系はUnit2に、8486系はUnit1に「個」が来るので、列の位置ではなく単位記号で判定させている。

つまずいたのはCSVのパースだった。 HS列のクォートを片側しか剥がしておらず、品目コードが一致しなかった。 クォートは両端に付いている。

// HS列は両端にクォートが付く。片側だけ剥がすとコードが一致しない
export const parseCsvRow = (line) =>
  line.split(',').map((c) => c.trim().replace(/^'|'$/g, ''))

パーサを直してSSOTを作り直したら、7,631.5円が出た。 Xの図と同じ数字が、今度は自分のリポジトリから出てきた。

ページ側で出た手直し

Vueのページをheredocで流し込もうとしたら、長すぎて途中で壊れた。 Writeツールに切り替えて作り直させた。

スタイルは既存のメモリメーカー系ページから引き継がせている。 隣のページと並べたときに揃わなくなるのが嫌で、新しいトンマナは作らせなかった。

トップのカード、一覧、ナビ、公表スケジュールにも登録させた。 その途中で、関連ページのリンクに本番では404になるページが混ざっているのを見つけた。 devサーバーでは開けてしまうので、リンク先を1つずつ当たらないと気づけない。

Chromeで開いて、さらに2箇所直させた。 統計品目番号が 854232921 の生コードのまま表に出ていた。 数量が億単位で、桁を数えないと読めなかった。

X軸から月が消えた

長期系列のチャートは、36ヶ月を超えると各年1月だけにラベルを間引く実装になっていた。 このページの系列は55ヶ月ある。 その結果、X軸には年しか出ず、右端が何月なのか読めなくなっていた。

間引き自体は残したまま、'YY/M の形を保つように直してもらった。 ラベル数が上限に収まる最小の間隔を3ヶ月、6ヶ月、12ヶ月の順に選ぶ。 拾う起点を先頭ではなく末尾に置いて、最新月には必ずラベルが付くようにした。 直近がどの月なのかを読めなくするくらいなら、古い側が欠けるほうがいい。

31ヶ月の韓国ページは閾値の内側なので、表示は変わっていない。

落ちた3件のテスト

このページ用のテストは20件になった。 最初の実行で1件だけ落ちた。

単価と数量から金額を逆算して照合するテストで、逆算値がわずかにずれた。 原因はデータ側ではなかった。 SSOTの金額を億円の小数1桁に丸めているので、逆算するとその丸め幅のぶんだけずれる。 許容幅を丸め幅から導出する形に直させた。

全体テストではさらに2件落ちた。 公表スケジュールのテストで、追加したエントリのimportが足りず、行順も「公表ラグの昇順」という規約に違反していた。 正しい位置へ移して通した。

スケジュールページには反映月2026-07、次回9/28頃と出るようになった。

毎月の更新をどう回すか

更新は /update-japan-chip-exports に閉じた。 差分を追記するのではなく、2022年からの全年を毎回取り直して全再生成する。 貿易統計は改定が入るので、速報値を固定して積み上げると系列の中で整合が取れなくなる。

e-Stat側の構造が変わって1品目も抽出できないときは、スクリプトが例外を投げて止まる。 黙って欠測にしない設計にした。

make-diaryチェーンにも組み込んだので、毎朝の流れの中で月末に拾える。

コミットとプッシュ

コミットの手前で学習ゲートに止められた。 ステージしようとしたら、別セッションが触っている途中のファイルが未ステージに混ざっていた。 巻き込まずに、自分の作業由来のものだけをステージし直させた。

同じころ、全体テストでスイートごと落ちているものを見つけた。 原因の調査はCodexに投げて、こちらはコミットを先に進めることにした。 学習ゲートは今回スキップした。

プッシュは1回はねられた。 originが3コミット先に進んでいて、中身は別マシンで作った日記のコミットだった。 svgManifest.json が両側で変わっていたので、先に退避してからマージした。 衝突は出ず、そのままプッシュできた。

学び

  • 図を見て最初にやることは、チャートを描くことではなく、同じ数字を手元で再現することだった。小数第1位まで合った時点で、あとの実装で迷う場所が無くなった。
  • 統計は「公表される日」より「その日に何が入るか」を見る。速報と確報で粒度が違うので、当番日を間違えると毎月空振りする。
  • 「実装に入ります」は成果物ではない。区切りでフルパスを聞けば、0件かどうかがその場で分かる。
  • テストが落ちても、データが間違っているとは限らない。今回は、丸めた値を逆算して比べていた側に問題があった。