家計簿アプリ Zaim には、健人が使ってる取込ルートが 2 系統 あるの。
claude-code-zaim(Cloudflare Workers 上で動く取込専用 Worker=サーバープログラム)が JR東日本サイトから取った利用履歴 CSV を Zaim に投入する仕組み)この 2 つは普段はキレイに棲み分けるんだけど、Suica オートチャージ(VIEW カードから自動でチャージされる仕組み)が走るとき、同じ金額が両方のルートから入ってきて二重カウントされる。放置すると家計簿の総支出が嘘の数字になって、月次の予算判断が歪むのよ。
このページでやるのは 設計のみ(実装は別ジョブで起票する)。具体的には、二重記録の発生条件を整理して、防止戦略を 5 案出して、推奨 1 案を選んで実装方針を固める、までを担当するわ。
Suica CSV 取込で発生しうる行は 3 種類。実際に二重記録になるのは パターン A だけと特定できた。
クレカ会社からは「VIEW カードで ¥3,000 の請求」、Suica 履歴からは「Suica に ¥3,000 入金」として記録される。同じ動きが「支出」と「収入」で 2 回登場するため、家計簿全体で見たときに支出も収入も嵩上げされる。これが 本タスクで防ぐべき唯一のパターン。
Suica 残高で支払った時点でクレカは関与しない。だからクレカ連携には絶対に出ない。取込しても重複は起きない安全な行で、これは普通に支出として記録するべき。
現金で券売機からチャージした場合、クレカ連携には出てこない。この場合チャージ行を取り込まないと、現金の動きが家計簿から消える。
ただし健人の現運用は モバイル Suica(オートチャージ運用) のみで現金チャージは発生しないため、YAGNI(You Aren't Gonna Need It=必要になってから足せばいい原則)で当面サポートしないと判断する。
| 案 | 方針 | 実装コスト | 副作用 |
|---|---|---|---|
| 推奨 案1 | 取込側でチャージ行を skip(収入+カテゴリ「その他」+内訳「電子マネー入金」を検知して捨てる) | 小(数行) | 現金チャージは記録から漏れる(運用上発生しない) |
| 代替 案2 | チャージ行を「振替」として変換登録(クレカ→Suica の口座間移動として 1 件で記録) | 中 | クレカ連携の支出と相殺ロジックが必要、設計が複雑化 |
| 案3 | 金額+日付±1 日でクレカ取引と突合(Zaim API /v2/home/money=Zaim の取引一覧を取得するエンドポイント で同期間取得 → 一致したら skip) | 大 | 突合の閾値判定が脆い。同金額が連続する日は誤検知 |
| 案4 | Suica CSV 出力段階で除外(fetch-history.sh(Suica履歴取得シェルスクリプト)で「電子マネー入金」行をフィルタ) | 小 | 家計簿ロジックがシェルに散らばる(責務分散の悪化) |
| 案5 | そのまま記録 + 月次レポートで突合警告 | 大 | 家計簿の数字は嘘のまま、人間の目視に依存 |
変更対象ファイル: ~/ghq/github.com/ramenumaiwhy/claude-code-zaim/src/handlers/importCsvHandler.ts(CSV 取込のリクエストを受け取って 1 行ずつ Zaim API に投げるハンドラ=「Webからの取込リクエストを受け付ける窓口」の役割)
変更箇所: 関数 importCsvHandler の行ループ内、現状の method === "振替" スキップ判定(行 289–293 付近)の直後に同型の if を 1 つ足すだけ。
// 現状 (行 289–293):
if (method === "振替") {
// Suica振替はYAGNIでサポートしない
skipped += 1;
continue;
}
// ↓ ここに追加 ↓
// Suica チャージ行 (収入 / その他 / 電子マネー入金) は VIEW カード等の
// クレカ連携で支出として既に取込済 → 二重記録防止のため skip。
// 現金チャージは現状のモバイル Suica 運用では発生しないため YAGNI。
if (
method === "収入" &&
category === "その他" &&
subCategory === "電子マネー入金"
) {
skipped += 1;
continue;
}
判定キーの根拠: ~/.claude/skills/suica-browser/scripts/fetch-history.sh(Suica 履歴を取得して Zaim 形式 CSV に変換するシェルスクリプト)の出力ロジック(行 203–207)で、入金行は必ず カテゴリ=その他 + カテゴリの内訳=電子マネー入金 でラベル付けされる。3 つすべて完全一致を要求することで、健人が将来手動で同カテゴリを使った場合の誤検知を防ぐ。
変更対象: src/handlers/importCsvHandler.test.ts(取込ハンドラのテストファイル)
imported=0, skipped=1, errors=[]、Zaim API は呼ばれないことを確認(skip パスが効くこと)imported=1, skipped=1(物販のみが Zaim に登録されること)| 項目 | 影響 |
|---|---|
既存 dedup(imported:suica:<hash> KV キー:「同一行を二度取り込まない」ための既存の重複防止機構) | 変更なし。skip 行は KV 書き込みもしない(既存の振替 skip と同じ挙動) |
| Zaim API 呼び出し回数 | 減る(チャージ行分の /v2/home/money/income 呼び出しが消える)。レート制限的にも有利 |
| クレカ連携側の挙動 | 無関係(Zaim 側の自動連携には触れない) |
| 過去取込済データ | 影響なし(今後の取込のみ)。既に二重計上された分は手動削除推奨 |
案 2(振替変換)を見送った理由: 「正しい家計簿表現」としては案 2 が理論上ベスト(クレカ→Suica の振替で 1 件記録すれば実態に一致する)。ただし Zaim 振替 API の調査と相殺ロジックの設計コストが高く、案 1 でも家計簿の総支出は正しい数字になる(過剰計上が消えるだけ)。過剰計上の解消が今回のゴールなので、案 1 で十分。将来「Suica の中身を独立した口座として可視化したい」要件が出てきたら案 2 に発展させればいい。
残課題(実装後に詰めるべき)
next_action:この設計で OK なら、別ジョブで実装起票(importCsvHandler.ts への数行追加 + テスト 3 ケース追加 + dry-run デプロイ確認)。NG ならフィードバックもらって修正 → 再レビュー。