• #tax-assistant
  • #外貨
  • #Gemini
  • #OCR
  • #SQLite
  • #Vue
  • #フルスタック
  • #日記
開発tax-assistantメモ

税務アシスタント外貨建てレシート対応 -- アルゼンチンペソの$問題から始まった一日

発端: ペソも「$」を使う

レシートOCRの結果を眺めていたら、金額欄に $ がついたレシートが混ざっていた。USドルだろうと思って流しかけたが、店名を見るとブエノスアイレスの飲食店。アルゼンチンペソだ。

ペソもドルも通貨記号は $。Gemini OCRが返してくる金額フィールドには $1,500 としか入っていない。これがUSD 1,500なのかARS 1,500なのか、記号だけでは判別できない。そのまま円換算すると桁が2つずれる。

手が止まった。

通貨記号から自動判別する仕組みを作り込むことも考えたが、今回は見送った。通貨コードのない $ を正確に判定するにはOCRの文脈解析が必要で、工数に見合わない。代わりに、「このレシートは外貨建てかもしれない」というフラグを人間が確認できる仕組みを作ることにした。


is_foreign_currency フラグのフルスタック実装

シンプルな真偽値フラグを、4つのレイヤーに通した。

1. Gemini OCRスキーマに外貨判定フィールドを追加

OCRのレスポンススキーマに is_foreign_currency: boolean を追加。Geminiが画像から読み取る際に、日本円以外の通貨記号や外国語表記を検出したら true を返すようにプロンプトを調整した。

# OCRスキーマに追加したフィールド(抜粋)
"is_foreign_currency": {
    "type": "boolean",
    "description": "日本円以外の通貨で記載されている場合true"
}

Geminiの判定精度に全幅の信頼は置けないが、明らかに外国語で書かれたレシートは拾える。拾い漏れは人間がUIで補正する想定。

2. DBマイグレーション

レシートテーブルにカラムを追加。デフォルトは 0(日本円)。

ALTER TABLE receipts ADD COLUMN is_foreign_currency INTEGER DEFAULT 0;

3. バックエンドAPI

4つの関数を修正した。

  • insert: OCR結果の is_foreign_currency をDBに書き込み
  • get_all: レスポンスにフラグを含めて返却
  • update: フロントエンドからの更新を受け付け
  • convert: MF仕訳CSV変換時にフラグを参照(外貨レシートに注記を付与)

既存のCRUD関数にカラムを1つ足す作業なので、各関数の修正自体は数行で済んだ。

4. フロントエンドUI

ReceiptForm.vue にチェックボックスを追加した。

<input type="checkbox" v-model="form.isForeignCurrency"
       :disabled="receipt.status === 'confirmed'" />
<span>外貨建て</span>

確定済みレシートではチェックボックスを disabled にして、誤操作を防いでいる。


外貨レシートの視覚的フィードバック

チェックをONにすると、レシートカードにオレンジのボーダーと「要チェック」バッジが表示される。一覧画面をスクロールしたとき、外貨レシートがオレンジの枠で囲まれて目に飛び込んでくる。

.receipt-card.foreign-currency {
  border: 2px solid #f59e0b;
}

視覚的に目立たせることで、確認漏れを防ぐ狙い。月次の経理作業でレシート一覧を流し見するとき、オレンジが目に入ったら立ち止まって円建て金額を確認する、という運用フローを想定している。


運用フロー

今回の実装は「フラグを立てる」ところまで。通貨種別の自動識別や為替レート取得は含めていない。

想定している運用は以下の通り。

  1. レシートをOCRにかける。Geminiが外貨と判定したら自動で is_foreign_currency = true
  2. 一覧画面でオレンジ枠のレシートを目視確認
  3. クレジットカード明細の円建て金額と突合し、正しい金額を手入力
  4. 確認が済んだらレシートを確定

クレカの円建て決済額が「正」で、レシートの外貨金額は参考値。為替レートを自前で取得して自動換算する仕組みは、需要が出てきたら別途検討する。


振り返り

真偽値フラグ1つを4レイヤーに通す作業は、各レイヤーの修正量は小さいが、動線を端から端まで確認する必要がある。OCRスキーマ → DB → API → UIの順に積み上げて、最後にE2Eで「チェックを入れてリロードしても状態が保持されるか」を確認して完了。

アルゼンチンペソの $ 問題は、通貨記号がグローバルに一意でないという当たり前の事実を突きつけてきた。$ はアメリカドル、カナダドル、オーストラリアドル、メキシコペソ、アルゼンチンペソなど20以上の通貨で使われている。記号だけで通貨を特定しようとするのは設計として破綻する。

今回は「自動判別しない」という判断を選んだ。フラグだけ立てて人間に委ねる。精度の怪しい自動判別より、確実に目に留まるUIのほうが実務では信頼できる。