韓国半導体輸出ページに速報値チャートを拡充した - DRAM/NAND単価・SSD/MCPをPDF速報から反映

開発financial-data

韓国半導体輸出ページに速報値チャートを拡充した

発端: PDFの速報単価は「答え合わせ」の材料になる

韓国の半導体輸出統計ページは、これまでKCS(関税庁)の確報ベースでDRAM/NAND輸出単価チャートを描いていた。朝、前日のデータ反映状況を確認していたら、単価チャートが更新されていないのは「7月15日頃の確報待ち」という仕様どおりの挙動だと分かった。

そこで逆に気づいた。月初に公表される政府の輸出統計PDF(速報)に載っている単価は、確報との突き合わせに使える重要な値だ。速報と確報の両方を並べれば、どちらかが間違っていても、何か違和感があっても気づける。既存チャートと同じ2024年1月起点で、速報単価のチャートを追加させることにした。

今日1日でページに入ったものを先にまとめておく。

  • DRAM/NAND輸出単価の速報値チャート(確報の長い線+PDF先行値の重ね描き)
  • SSD・MCPの品目別チャートへの6月速報値の反映(KPIには速報ラベル)
  • 全14チャートのモーダル拡大(クリックで開く、再クリックまたはEscで閉じる)
  • X軸の「24/1」形式への統一と、データ未着月の空ラベル表示
  • 取得手順書の速報対応への更新と、テスト89件+E2E 1本

過去号を掘ったら、単価の行は最新号が初出だった

まず過去のPDFがどこまで遡れるかの調査から。前日に整えたPDF取得手順のドキュメントを読ませて、データ源と過去分の取得可否を確認するところから始めた。

アーカイブの一覧ページは1ページ目に直近2号しか出ない。ページングのパラメータ(ページ番号と件数)を調べさせ、まず検索したら総0件。検索語をEUC-KRにエンコードし直したらようやく48件ヒットした。今度はタイトル抽出のパターンが合わず、生HTMLの構造を見させて修正。コンソールのcp932問題で文字化けするので、一覧はJSONに書き出させて確認した。

そこから6体のエージェントを並列で派遣して、全号のPDFを機械抽出させた。すると重要な事実が判明した。「半導体輸出単価(万ドル/kg)」の行は2026年6月号(7/1公表)が初出で、2024年1月〜2026年5月の28号には存在しない。旧号に載っているのは市況の固定価格(ドル建て)で、輸出単価とは別物だった。つまりPDF先行の単価は、現時点では2026年4月〜6月の3点だけ。

「2024年から遡れるはず」という前提で頼んだが、データ自体が存在しなかった。チャートは予定どおり2024年1月起点にして、KCS確報から再計算した長い線に、PDF先行値の3点(今後毎月1点ずつ伸びる)を重ねる構成で作らせた。

  • SSOTのデータファイルを新設し、ページに比較チャートのパネルを追加
  • 出所・注記セクションに先行単価の出典を追記
  • 取得手順書も速報単価の反映手順に合わせて更新
  • テストはヘルパー37件を含む89件が全部通り、devのSSRで新パネルと線の終端ラベル(各系列の最新値)を確認

モーダル拡大の統一とマゼンタ枠での目視確認

チャートの操作系は他ページと揃えたい。マウスオーバー→クリックでモーダル拡大、再クリックまたはEscで閉じる挙動を、他ページの実装を探させてこのページにも適用させた。モーダルラッパーを作る前に、他のチャートコンポーネントのルート要素の幅制約を確認させてから、ページ内の14チャートすべてを ExpandableChartPanel で包む形にした。

作ってもらったチャートの表示が見切れていたので修正させ、凡例の座標がviewBox内に収まっているかまで検証させた。

今回どのチャートが新規なのか、外枠のグレーだけでは見分けがつかない。そこで新規追加分を一時的にマゼンタ枠で囲ませて、目視で1つずつ確認した。

途中、新規コンポーネント追加でdevサーバーの再起動が固まる既知パターンを踏んだ。issueに記録してからポート3000のプロセスを止めて再起動——のはずが、Bash側で変数が展開されてしまい、シングルクォートで再実行してようやく復旧。最後にモーダルの最小のE2Eスペックを1本書かせて通した(初回の失敗はハイドレーション完了前にクリックするテスト側のタイミング問題で、モーダル自体は正常だった)。

確認が済んだのでマゼンタ枠を外させて、「コミットしといて」と頼んだら、作業の区切りごとに既にコミット済みだった。積み残しもコード面はゼロを確認して、午前の部を締めた。

SSDとMCPも速報から更新できることに気づいた

その後、速報PDFをHTMLテーブル化したものを眺めていて、もう一つ気づいた。単価はチャートに追加したのに、SSDとMCPの品目別金額も速報から取れるのにまだ入っていない。速報ベースで更新できるものは更新するフローに、スキル側を変えるよう指示した。

実装前に、指摘したHTML(PDF由来)の6月値の裏取りをさせてから着手。速報では埋まらないフィールドがあるので、ヘルパー関数をnullセーフに直させた上で、MCPとSSDのデータセットに6月の速報値を追加した。KPI表示には速報ラベルとフォールバックを付けさせ、weightTons を参照する他の箇所がnullで壊れないかも横断確認。ページの品目別セクションのリード文にも速報の説明を反映させて、テスト全パス。トップページのカードは具体的な月次値を持たない文言だったので追従不要だった。

「入ってない?」→ 入っていた → でも欠損月が分かりづらい

devで画面を見て「結局チャートに入ってないと思うんですけど、入ってますか?」と伝えたら、devサーバーの描画そのもののSVGが2枚返ってきた。確かに入っていた。ごめんなさい、と返した。

ただ、見間違えたのには理由がある。DRAMなどの月次チャートはX軸の表記が他のチャートと違っていて、最新の棒が26年5月なのか6月なのか、画面を見ても判別できなかった。そこでX軸を「24/1」「26/6」の形式に変えさせた。

さらにもう一段。SSDのように6月分が埋まっている品目と、DRAMのように5月までしかない品目を並べて見比べたとき、「まだ入っていない」ことが分かるようにしたい。6月データがない品目にも '26/6 の空ラベルを立てて、欠けている月を空カラムとして可視化させた。

実装は、系列の末尾を指定月までnullで埋める純粋関数をデータファイルに追加し、ページ側で全品目のバーチャートを最大月までパディングする方針。

// {month, value} 系列の末尾を toMonth まで value: null で埋める。
// 品目間で最新月が揃わないとき、X軸グリッドを揃えて
// 「データがまだ無い月」を空カラムとして可視化する。
export const padSeriesToMonth = (
  series: { month: string; value: number | null }[],
  toMonth: string,
) => { /* ... */ }

最新のデータがあるバーにはマゼンタの目印を付けさせた。テストも追加して全パス。

次の答え合わせ

7月15日頃にKCSの確報が出たら、今回入れた速報3点と初めて突き合わせられる。速報チャートは今後毎月1点ずつ伸びていくので、月初のPDF速報反映→月中の確報反映、という二段の運用がこのページの定常フローになる。

学び

  • 速報と確報の二重化は「答え合わせ」の仕組みとして効く。片方だけでは、間違っていても気づく手段がない
  • 「過去に遡れるはず」という思い込みは危ない。単価の行は最新号が初出だと、28号を機械抽出して初めて確定した
  • 「入っていない」と思ったら、まず描画そのものを出させて確認する。今回は入っていて、本当の問題は「欠損月が見えない」表示側にあった
  • 確認用のマゼンタ枠は安上がりで確実だった。どれが新規か、どのバーが最新か、一目で追える
  • 品目ごとに最新月がずれるデータは、揃っていないこと自体を表示に出す。空ラベルのパディングで「無い」が見えるようになった