FAXで届く精算書を、AIに読ませるまで
FAXとPDFで届く中古車の請求書・精算書の手入力を自動化した記録。OCR、Document AI と作り直し、半年かけた仕組みを2日で Gemini に置き換えるまでの悪戦苦闘と、いまも外していない検算の話。
使ったもの
Vertex AI (Gemini 2.5 Pro / Flash) / Google Cloud Vision / Google Apps Script / Cloud Run / Google Chat
中古車の仕入と売却では、いまでも請求書や精算書がFAXやPDFで届きます。車両代金、手数料、消費税、経費、ペナルティ。それを1枚ずつ読んで、スプレッドシートに打ち込む。
当社でもこの作業を溜め込んでしまい、私が半日近くかけて片づけることがありました。
いまは、すべて自動で回っています。ただ、一気に自動化できたわけではありません。最初に「よくできた」と思った仕組みは、精度が悪くて捨てました。次に作った仕組みは、半年かけて育てたあとで、やっぱり捨てました。
結論から言うと、半年の苦労は「相手のシステムの内部構造に合わせようとした」ことから来ていました。それをやめた瞬間に、2日で片づきました。
この記事では、その悪戦苦闘をそのまま書きます。うまくいった話より、うまくいかなかった話のほうが長くなります。
何に困っていたか
扱う帳票は、全部で7種類あります。仕入のときの請求書が数種類、売ったときの支払明細書と精算書です。
請求書は仕入れた台数分だけ届き、支払明細書と精算書は週に1通ずつ。足し合わせると、月に50通を軽く超えます。
届き方は2通りです。複合機から出てくるFAXと、メールに添付されたPDF。ある仕入先の精算書はパスワード付きPDFで届くので、開くだけでひと手間かかります。
やること自体は単純です。書類を見て、必要な項目を打ち込む。それだけです。
だけど、1回やると1〜2時間かかりました。台数が多い週は4時間。私は週に1回まとめて片づけるようにしていましたが、面倒なものは後回しになります。
(正直に言うと、面倒でした。)
後回しにすると、次の週の分が積み上がる。気がついたら2週間、手をつけていない。そんなことが実際にありました。
なぜ、すぐには片づかなかったのか
自動化は、最初から考えていました。手で打ち込む作業を続けるつもりはなく、できるところから少しずつシステム化を進めていました。
ただ、帳票の読み取りだけは、なかなか片づきませんでした。理由は2つあります。
一つ目は、精度です。PDFソフトに付いているOCR機能を試したことがありますが、文字は起こせても読み違いが多く、結局は見直しが必要でした。
二つ目は、繋がらないことです。OCRで文字を起こしても、それを自分たちが使っているスプレッドシートの仕組みに流し込む手段がない。読み取ったあとに、また人が転記することになります。
そもそも、この種の帳票は取引先ごとにレイアウトが違います。項目の名前も位置も並び順も揃っていません。だから「どれが車両代金で、どれが手数料か」は、文字を読むだけでは判別できないのです。
だから、読み取りの部分だけは人がやる。そういう状態が続いていました。
いま振り返ると、この段階で「他に方法がない」と決めつけていたことが、いちばんの遠回りでした。方法はあったのです。しかも、私が毎日使っているツールの中に。それに気づくまで、半年以上かかりました。
やったこと
ここからは、実際に作ったものを時系列で書きます。3つの世代に分かれます。
第1世代:OCRと正規表現は、1か月で行き詰まった
最初は素直に、Cloud Vision OCRで文字を起こして、正規表現で必要な項目を抜き出す方式を作りました。
すぐに行き詰まりました。帳票のレイアウトが少し変わると、正規表現が当たらなくなるのです。
取引先ごとにパターンを書き足していくと、増やすほど壊れやすくなる。1か月ほどで「これは無理だ」と判断しました。
第2世代:Document AI に半年かけたが、半分は読み違えていた
次に、Googleの Document AI でカスタムプロセッサを作りました。実際の請求書をかなりの枚数読み込ませて、「ここが金額」「ここが車台番号」と位置を教えていく方式です。
これを半年続けました。この記事でいちばん長い部分です。
いちばん困ったのは、表の行がバラバラになることでした。
Document AI は、抜き出した項目を「金額、金額、車名、金額、車台番号……」という平坦なリストで返してきます。どの金額がどの車の行に属するのかは、こちらで組み立て直す必要があります。
そこで、さまざまな判定を積み上げました。
たとえば、文字の位置が近いものを同じ行とみなす。
たとえば、金額は車名より必ず右にある、と決める。
たとえば、枠の縦位置が50%以上重なっていたら同じ行とみなす。
最後にたどり着いた方針は、「正しく読む」ではなく「怪しい行は捨てる」でした。必要な項目が揃わない行は、行ごと使わない。ある仕入先の請求書では、明細行と車台番号の対応づけを最適化問題として解くところまでやっています。
(いま見返すと、よくそこまでやったなと思います。)
だけど、直しても別のところが崩れるのです。1つの帳票に合わせると、別の帳票が読めなくなる。同じ問題を、何度も直していました。
しんどかったかと聞かれると、当時はそれが日常だったので、そこまでではありませんでした。ただ、この「また崩れた」という再発だけは、いま思い返しても堪えました。
では、肝心の精度はどうだったのでしょうか。
体感で、半分くらいは間違っていました。 特に、1つの項目が2行にまたがって書かれていると、ほぼ確実に読み違えます。
だから、スプレッドシートに自動で書き込まれても、結局すべて目で確認していました。自動化したはずなのに、確認する仕事が増えている。そういう半年でした。
転機:Gemini にPDFを1枚渡したら、正確に読めた
私は普段から、画面のスクリーンショットを Gemini に投げて内容を読ませることをよくやっていました。それが十分に正確だったので、ある日、請求書のPDFをそのまま渡してみたのです。自社宛の書類を1枚だけ。
正確に取れました。
半年かけて教え込んだ仕組みが読み違えていたものを、何も教えていない相手がそのまま読んだ。それだけの話です。だけど、その場で方針を変えました。
第3世代:Vertex AI 経由の Gemini に、2日で置き換えた
置き換えの作業は、2日で終わりました。半年やったものが、2日で。
進め方は、帳票1種類ずつ。簡単なものから難しいものへ、という順番です。スプレッドシートに書き込む既存の処理には手を入れず、AIの出力を既存の形式に変換する小さな部品を1枚挟みました。
現在の構成は、次のとおりです。
- メールを監視し、添付PDFを Drive の決まったフォルダに移す
- FAXは Cloud Vision OCR で「受け入れるべき帳票か」だけを判定する
- パスワード付きPDFは Cloud Run 上で解除してから保存する
- Drive を1分おきに監視し、新しいPDFがあれば1ページ目をOCRして帳票の種類を判定する
- 種類ごとに、PDFを丸ごと Vertex AI 経由の Gemini に渡して項目を抜き出す
- 検算する
- スプレッドシートに書き込み、内容に合わせてファイル名を変え、期・月別のフォルダに振り分ける
- Google Chat に通知する
モデルは2つ使い分けています。精算書と支払明細書は gemini-2.5-pro、請求書系の4種類は gemini-2.5-flash です。前の2つは読み取りが難しく、flash では通らなかったためです。
なお、Cloud Vision は捨てていません。「読み取る」役目からは降ろしましたが、「これは何の書類か」を見分ける入口としては今も動いています。全部をAIに替えたというより、向いている仕事に配置し直した形です。
AIの出力をそのまま信じている箇所は、ひとつもない
ここまで読むと、「Gemini に替えたら解決した」という話に見えるかもしれません。だけど、私はAIの答えをそのまま使っていません。検算は、いまも外していないのです。
まず、帳票に印字された合計と、抜き出した明細の合計を突き合わせます。合わなければ、各項目を10倍・10分の1にして一致するものがないか、総当たりで探します。桁を読み違えたときのための処理です。
書き込んだあとにも、もう一度検算します。プログラムが計算した値ではなく、スプレッドシートの数式が実際に出した値を読み戻して比べるのです。一致したセルだけ、背景を灰色に塗る。塗られていないセルが「人が見るところ」になる、という設計です。
AIに渡している指示文にも、実際に起きたことの痕跡が残っています。
桁数注意。特に数千円台を数百円台と読み間違えないこと
絶対に「税抜金額 × 10%」のように消費税を推測・計算しないこと。PDF 上の 2段目の値(または空欄)だけが正しい消費税額です。
消費税のルールは、いちど決めて翌日に撤回しています。最初は「この項目は非課税だから除く」と、帳票の意味で判断させようとしました。うまくいきませんでした。
最終的には「2段目が空欄なら非課税」という、印字の見た目のルールに落としました。意味を考えさせるより、紙の上に何が書いてあるかを見せたほうが、正確だったのです。
数字
ここまでの取り組みで、何がどう変わったのか。数字で整理します。
| 従来 | 現在 | |
|---|---|---|
| 処理にかかる時間 | 1回あたり1〜2時間(多い週は4時間) | 月に1回、1〜2分 |
| 頻度 | 週1回まとめて | 常時自動 |
| 滞留 | 2週間放置することもあった | なし |
| 目視チェック | 全件(Document AI 期も含めて) | ほぼしていない |
月に処理する帳票は50通以上、7種類です。
いま残っている作業は、月に1回「きちんと動いているか」をざっと眺めるだけで、1〜2分で終わります。決算のときはもう少し細かく見ますが、それも年に1回です。
Document AI のころは、体感で半分くらい間違っていました。Gemini にしてからは、ほぼ間違いは起きていません。
それでも、検算は外していません。金額を扱っている以上、精度が高いことと、確認しなくていいことは、別の話だと考えています。
うまくいかなかったこと
うまくいかなかったこと
成果だけを書くと、都合のいい記事になります。ここでは、うまくいかなかったことを並べます。
半年かけたものが2日で置き換わった。それ自体がいちばんの失敗です。 粘れば精度は上がると思っていました。他に方法がないとも思っていました。どちらも違っていました。
Document AI で「一部の項目がどうしても取れない」という状態は、最後まで解消できませんでした。プロセッサを親子構造で作り直したときは、自前の判定ロジックを258行削除できて、精度も上がりました。だけど、残った問題はそのままです。その状態で、5か月放置していました。
本筋でないところにも、本筋と同じだけ時間を使いました。プロセッサを使うときだけ起動し、使わないときは止める仕組みを作ろうとして、17日間で3回、全面的に書き直しています。当時のコミットメッセージにこう残っています。
理由:アンデプロイするトリガーが失敗して、アンデプロイできず、トリガーを作成→アンデプロイ失敗が繰り返されていたため。
さらに、いま履歴を読み返すと、おかしな箇所が見つかりました。Document AI を呼ぶはずの関数の途中に、この制御コードが挿し込まれたままコミットされていたのです。つまり、その関数は Document AI を呼んでいませんでした。そんな状態のものが、しばらく残っていました。
当時のコードには「既存実装が無い場合は警告のみ(実処理は未実施)」というコメントも残ったままです。なぜその仕組みが必要だと考えたのか、いま履歴を読んでも分かりません。
Gemini への移行も、それで終わりではありませんでした。移行作業の巻き添えで、ファイル名を変更する処理と、帳票の種類を見分ける処理が消えていました。原因は特定できていません。しばらく気づかないままでした。
精度の問題だと思っていたら、こちらのバグだったこともあります。金額を万円単位に変換するところで小数の誤差が出て、32,400円が「3.2399万」になっていました。AIは正しく読んでいたのです。
移行時に挟んだ変換の部品も、そのまま残っています。AIは整数を返してくるのに、既存の処理は「1,234,567円」という文字列を前提にしています。だから、いったん文字列に戻してから渡している。動いてはいますが、きれいではありません。
次にやること
正直に言うと、まだこれからです。
書類の入力から離れられて、「このデータから何が分かるのか」「次はどこを自動化できるのか」と考える時間はできました。ただ、そこから具体的に何かを始めたわけではありません。日々の状況を見て、直せるところがあればすぐ直す。いまはそういう段階です。
さいごに
ひとつだけ、この一連の作業ではっきりしたことがあります。
同じ時期、別の仕組みでも似た判断をしていました。取引先のサイトから情報を取る処理です。ブラウザを自動操作する方式がうまく動かず、最終的にメールの添付を待ち受ける方式に変えました。書類のほうも、Document AI のプロセッサを細かく調整する方式から、PDFを丸ごと渡す方式に変わりました。
どちらも、相手のシステムの内部構造に合わせるのをやめた、という同じ変化です。
相手の構造に合わせにいくと、相手が変わるたびにこちらが壊れる。半年かけて学んだのは、技術の選び方ではなく、そこでした。
同じ種類の書類が、同じようにFAXで届いている会社は多いはずです。私の半年が、どこかで誰かの半年を短くできれば、この記事を書いた意味があります。
今回参照した情報源
- Google Cloud Vision(OCR) https://cloud.google.com/vision
- Google Document AI https://cloud.google.com/document-ai
- Vertex AI(Gemini) https://cloud.google.com/vertex-ai
- Google Apps Script https://developers.google.com/apps-script
