スプレッドシートのままでなく、月別CSVで残す理由

シートは「いま」を見る道具で、履歴を追いにくい

モール別の売上・広告シートは、日々の貼り付けと集計には向いています。ただ、ある月の数字がいつ、どう変わったかを後から追うのは難しく、数字が合わなくなったときの原因調査が重くなります。

月別CSVにして版管理つきの置き場へ保存すると、更新のたびに差分が残ります。「この月のこのモールの広告費が、いつ変わったか」を履歴から確かめられます。

読むのが人だけではなくなった

relmea が関わっている運用では、AIエージェントが週次レビューなどでこのCSVを読みます。シート全体を渡すと、関係のない年度やモールまで読ませることになります。

月・モール・種別で1ファイルに分けておけば、AIは必要なファイルだけを開けます。数字の変化を追うときも、差分の履歴がそのまま材料になります。

経緯:DBつきの管理アプリから、単体ツールへ

以前は、自作の管理アプリ(DBと画面つき)の一機能として、スプレッドシートからCSVを保存していました。DBサービスの無料枠が逼迫して使用を中止し、2026年6月11日にこの機能だけを、DBにも画面にも依存しないコマンドラインの単体ツールへ移しました。同日に元アプリのDBとホスティングは解体しています。

同期は手動実行です。常駐させず、必要なときに1コマンドで回す形にしたことで、止まる理由になる部品がほとんど無くなりました。

置き場の形は「モール/種別/年度/月」で決める

保存先のパスは次の形に統一しています。

  • {モール}/data/{種別}/{年度}/YYYY-MM.csv

1か月が1ファイルです。年度は4月始まりで、各行の日付の月から年度を判定します。4月分は新しい年度のフォルダに入ります。

コミットのメッセージも「auto: {モール} {種別} {YYYY-MM} データ更新」の形に固定しています。履歴の一覧を見るだけで、どのモールのどの種別の何月分が更新されたかが分かります。

2026年度の対象は、7チャネル・16タブです。

  • Amazon:商品・広告の2タブ
  • Amazonの別アカウント:商品・広告の2タブ
  • 楽天:商品・RPP広告・クーポンアドバンス広告の3タブ
  • Yahoo!ショッピング:商品・広告の2タブ
  • Shopify:商品・注文・Google広告・Yahoo!広告・Meta広告の5タブ(Meta広告は2026年度に新設し、2026年9月16日から)
  • LINEギフト:日次の1タブ
  • カウシェ:日次の1タブ

書き込みは1ファイルずつ行い、既存のファイルがあれば上書き、無ければ新規作成します。失敗したファイルだけをエラーとして数え、残りは進めます。1か所の失敗で全体が止まらない作りです。

モールごとの整形ルールと、日付の正規化

整形ルールは8種類

シートの形はモールやレポートごとに違うため、タブごとに変換ルールを割り当てています。実際に使っているのは次の8種類です。

  • そのまま出力(日付の正規化と月別の振り分けだけ)
  • 楽天の商品データ:A列が空のメタ行を飛ばし、#・ジャンル・カタログID・商品ID・商品名の5列と、#N/Aの列を除く
  • 楽天のRPP広告:そのレポートにしか無い列名「コントロールカラム」で見出し行を見つけ、末尾の#N/Aの列を除く
  • 楽天のクーポンアドバンス広告:同じ考え方で、列名「クーポン獲得数」から見出し行を見つける
  • Shopifyの商品
  • ShopifyのGoogle広告:「キャンペーン レポート」の行、日付範囲の行、「--」の行、「合計:」を含む行を除き、見出し行ごとに列名から列番号を作り直して、出力する列の順番を固定する
  • ShopifyのYahoo!広告:「合計」「小計」の行を除く(列の読み方は次の章で扱います)
  • 日次のピボット表から売上CSVへの変換(LINEギフト・カウシェ)

共通しているのは、見出し行の位置を行番号で決め打ちしないことです。レポートの前に何行のメタ情報が付いても、そのレポート固有の列名で見出しを探せば拾えます。

日付は3つの形をそろえる

シートの値は書式を当てない形(Google Sheets API の UNFORMATTED_VALUE)で読んでいます。そのため、日付のセルは「45000」台のようなシリアル番号で届きます。タブによっては「2025/4/1」や「2025-04-01」の文字列で日付が入っています。

この3つの形をすべて YYYY-MM-DD にそろえます。日付として読めない行(メタ行・見出し・合計行)は、この時点で捨てます。どの月のファイルに入れるかがこの日付で決まるため、ここが曖昧だと月の振り分けそのものが狂います。

列の位置で読むと、エラーなしでずれる

実際に起きたこと

ShopifyのYahoo!広告のタブで、2026年度のタブだけ、I列に「広告グループの検索クエリーマッチング」という列が増えていました。当初は列の位置で読んでいたため、インプレッション数から右が1列ずつずれ、「コスト」の列にクリック率が入ったCSVができていました。

厄介なのは、エラーが1件も出なかったことです。CSVは作られ、保存も通り、中の数字だけが違っていました。終了コードやエラー件数を見張っていても、この種類の壊れ方は捕まりません。

修正:見出し行が出るたびに、列名から列番号を引き直す

2026年10月1日に、読み方を次のように直しました。

  • 見出し行(「配信設定」で始まる行)が出るたびに、列名から列番号の対応を作り直す
  • 出力する列は、列の位置ではなく列名で取る
  • 該当する列が無ければ、その列は空欄にする

このタブは貼り付けのたびに見出しが繰り返され、列の構成も年度で違います。見出しを最初の1回だけ読む作りでは、途中で構成が変わった貼り付けに追従できません。見出しが出るたびに対応を作り直すのは、そのためです。

空欄は、別の数字に化けるよりましな壊れ方

列が無ければ空欄で出す方式なので、元の列が消えても処理は止まりません。その代わり、空欄が並んだCSVは中を見た時点で異常だと分かります。コストの欄にクリック率が入っているCSVは、一見すると普通の数字の並びです。気づける壊れ方を選ぶ、というのがこの修正の考え方です。

年度切替と過去年度の凍結、範囲を絞った実行

確定した年度は、対象から外す

2024年度と2025年度は同期済みとして対象の一覧から外し、毎回の実行では現行の2026年度だけを上書きしています。過去年度のCSVは、一覧から外した時点でこのツールからは書き換わらなくなります。

シート側で過去の数字に手が入っても、確定させたCSVには反映されません。毎回の実行範囲が現行年度に限られるので、何が変わったかも確かめやすくなります。過去年度を直す必要が出たときは、年度を指定して意図して流します。

年度が変わったら、設定ファイルに追記する

スプレッドシートは年度ごとに別ファイルです。年度が変わったら、新年度のスプレッドシートIDの組を設定ファイルに追記し、対象を新年度へ切り替えます。モールごとの整形ルールの側は触りません。年度で変わるもの(どのファイルを読むか)と、年度で変わらないもの(どう整形するか)を分けておくと、切替の作業が1か所で済みます。

実行の前に、出力だけを確かめる

実行のオプションは次のとおりです。

  • --dry-run:保存せず、出力されるファイルのパスと行数だけを表示する
  • --mall:モールを指定する
  • --type:種別を指定する
  • --year:年度を指定する

モール・種別・年度で絞れるので、1つのタブを直したときに保存の範囲をそのタブだけに限れます。結果はタブごとに OK/SKIP/ERR とファイル数で表示され、1件でも ERR があれば終了コードが1になります。読めなかったタブが黙って抜け落ちることは、これで防げます。ただし前の章の列ずれのように、正常終了したまま中身が違う壊れ方は、この表示では分かりません。

権限の設計と、人とAIの分担

シートは読み取り専用、トークンは置かない

スプレッドシートは、読み取り専用の権限だけを持つサービスアカウントで読みます。このツールからシートへは書き込めません。変換に誤りがあっても、元データは壊れません。

GitHub側は、固定の個人アクセストークンをファイルに置くのをやめました。実行のたびに、GitHub CLI のログイン済みトークンを取得します。置いたトークンが失効して止まる問題を避けるためです。鍵と設定ファイルは、版管理の対象から外しています。

鍵を消す前に、作成日と用途を確かめる

2026年5月16日、鍵の棚卸しで、この同期に使うGitHubのトークンを削除候補に挙げかけました。根拠は「一度も使われていない」「古いアプリの名前が付いている」の2点だけでした。実際には、作ったばかりで、これから使う鍵でした。

それ以来、鍵を削除する前には作成日と用途を確認しています。管理アプリを解体した日も、旧アプリの名前が付いたクラウドのプロジェクトとサービスアカウントは新しいツールが使っているので削除しない、と記録に残しました。名前が古いことは、使われていない証拠になりません。

人とAIの分担

人が持つのは、年度が変わったときの設定の追記、同期の実行、dry-run の結果の確認です。AIは、できあがった月別CSVを読んで週次のレビューなどに使います。

AIに任せているのは読む側だけです。変換と保存は、決まったルールで毎回同じように動くコマンドにしてあります。読む側が誤っても次の回で読み直せますが、保存する側が誤ると、誤った数字が履歴として残るためです。

まとめ

長く運用して効いたのは、次の5つでした。

  • 月別のファイルにして、差分の履歴を残す
  • 列は位置でなく列名で読み、見出しが出るたびに対応を作り直す
  • 確定した年度は対象から外して凍結する
  • dry-run と範囲の指定で、保存の前に確かめる
  • シートは読み取り専用で読み、トークンは置かない

列ずれは、エラーを出さずに起きました。初期構築のあとで効いてくるのは、こうした壊れ方を前提にした設計です。まずは自分のシートを、列の位置で読んでいる箇所が無いか確かめるところから始めてみてください。