経費精算のレシート照合で部分一致を使う限界|AI-OCRなどの候補抽出と人の確認

読了 約12分 たびすけ
レシート内の金額文字列候補と人による照合確認を示す図

次に読む記事

関連するテーマの記事を、先に確認できます。

1枚のレシートを複数の経費明細へ分けた申請を考えます。レシートに書かれているのが合計金額だけなら、合計と申請全体は合っていても、分割後の各明細と同じ数字が見つからないことがあります。明細単位の候補が見つからないことは、ただちに申請の誤りや不正を意味しません。

この例が示すのは、レシート内の文字列候補と業務上の照合を別の状態にすることです。合計欄・明細欄・通貨・符号・税込/税抜・税率別金額・按分・複数証憑との対応を確認してから、人が判断します。

Python公式リファレンスが説明する x in y は、文字列またはbytesの中に別の文字列が部分文字列として現れるかを判定します。したがって str(abs(int(amount))) in flat の True は「数字の並びがどこかにあった」という候補であり、「証憑の正しい欄と申請金額が一致した」という意味ではありません。経理・管理部門で機械判定を組むなら、この二つを最初から別の状態として持たせます。

候補抽出と照合を分ける

金額文字列の候補が見つかる → 照合候補として記録。欄・通貨・符号・税込/税抜・税率・按分・証憑の対応が確認できる → 会社の規程に従って人が判断。候補の有無だけでは自動OKにしない。

部分文字列の一致が示すもの

候補抽出の意味を、次の小さな関数で固定します。これはベンダーのAPI仕様や運用コードではなく、金額文字列を探すロジックだけを示した例です。

import re

def amount_in_text(amount: int, text: str) -> bool | None:
    """金額文字列の候補を返す。欄や符号などの意味は判定しない。"""
    if not amount:
        return None
    flat = re.sub(r'[\s,,]', '', text)
    return str(abs(int(amount))) in flat

flat では空白・改行・カンマを取り除くため、見た目の区切りを無視して数字を探します。負の金額には abs を使うので、符号もこの関数の判定対象から外れます。Pythonの真偽値の説明では、数値の 0 は偽として扱われます。そのため if not amount は、0円で True も False も返さず、未判定の None へ分岐させます。

5ケースで候補と照合を切り分ける

次の表は、提示された関数ロジックを5つの小さな入力へ当てた独自検証です。公式サンプルや実際の申請を集計した結果ではありません。どの行も、関数の戻り値を合計金額の一致や承認の完了へ読み替えないでください。

申請金額証憑テキスト関数の結果結果から分かること
1000TOTAL 10000True1000が部分文字列に含まれるだけで、合計が1,000円とは確定しない。
1000TOTAL 1 000True空白を除いた後に候補になるが、欄の対応は分からない。
-1000TOTAL 1000Trueabsで符号を失うため、正負の意味は一致確認できない。
0TOTAL 0Noneif not amountで未判定になり、候補なしとは言えない。
1000ITEM 1000 TOTAL 3000True明細と合計のどちらの数字かを識別しない。

たとえば最後の行で必要なのは、数字が現れたという記録だけではありません。申請の1,000円が明細なのか、証憑の合計3,000円を按分した結果なのか、複数の証憑をまとめた金額なのかを、申請と証憑の対応関係から確かめることです。部分文字列の検出結果だけでは、そこまで進めません。

テキスト抽出は欄の意味を返さない

PyMuPDFの Page.get_text() は、ページの内容から文字列などを抽出するAPIです。同じ公式ドキュメントにはOCR用の Page.get_textpage_ocr() も別のメソッドとして示されています。テキスト層から文字を取得できたことと、画像をOCRで読んだことは、処理の入口から分けて扱えます。

import fitz

def extract_text(path: str) -> tuple[str, str | None]:
    """テキストと抽出エラー状態を返す例。"""
    try:
        with fitz.open(path) as doc:
            text = ''.join(page.get_text() for page in doc)
        return text, None
    except Exception:
        return '', 'extraction_error'

抽出結果を候補判定へ渡す前に、エラー状態を分岐させます。extraction_error なら amount_in_text を呼ばず、「要確認(抽出エラー)」で止めます。抽出に成功して候補がない場合だけ「候補なし」へ進み、amount=0 は関数内で未判定の None のままです。

text, extraction_error = extract_text(path)

if extraction_error:
    verdict = "要確認(抽出エラー)"  # amount_in_textは呼ばない
elif not text:
    verdict = "要確認(文字なし)"
else:
    amount_candidate = amount_in_text(amount, text)
    verdict = (
        "要確認(金額候補あり)" if amount_candidate is True
        else "要確認(金額候補なし)" if amount_candidate is False
        else "要確認(金額未判定)"
    )

この例で戻るのはページの文字列だけです。出てきた数字が合計欄・明細欄・税額欄のどこに属するか、また業務上の照合対象かは、この戻り値からは決まりません。画像をOCRへ回す設計でも、文字が得られた時点で照合完了にはならず、同じ欄の確認が必要です。

特定サービスのAPI仕様や、実際の申請件数・精度・費用・承認ログまでを、この候補抽出の例から導くことはできません。AI-OCRを導入する場合も、サービスの応答品質や再現性は自社のデータと規程で別に検証します。ここで扱うのは、文字列候補と業務照合の境界です。

税率別の金額と証憑の欄を対応づける

国税庁のインボイス記載事項は、売手の名称・登録番号、取引年月日、取引内容、10%・8%それぞれの対価の総額と適用税率、税率別の消費税額などを区別して示しています。一つの数字が見つかっただけでは、その数字がどの記載事項に対応するか、税率別の総額や税額なのかを結び付けられません。

登録番号がない表示を見つけた場合も、その一点だけで個別証憑の税務上の適否を自動確定しません。簡易インボイスでは一部項目の省略が認められるため、証憑の種類、実際の記載内容、会社の処理規程をそろえて確認します。一般的な記載事項の説明だけから、個別の仕入税額控除の可否まで断定することもできません。

3つの仮想例で要確認の理由を追う

次の3例は、候補抽出の結果を業務照合へ読み替えないための机上例です。実際の申請件数、精度、費用、再現性、サービス一般の性能を示すものではありません。

合計は合っていても分割計上

1枚のレシートの合計を複数明細へ分けると、レシート全体の合計と申請全体は整合していても、各明細がレシート内のどの欄に対応するかは別問題です。部分文字列の候補だけでは按分の理由や申請方法の妥当性まで決められず、要確認として人が見る地点に残します。

申請は10%、レシートには8%が混ざる

申請明細が10%でも、レシート側に軽減8%や対象外の記載が混ざることがあります。数字の候補が見つかるかとは別に、税率ごとの対価・税額と申請の税区分を対応させる必要があります。部分一致の結果やOCRの読み取りだけで税区分を確定しません。

登録番号のない予約確認書やフリマの画面

予約確認書やフリマの取引画面など、登録番号のない証憑を想定します。登録番号がないという事実は要確認の材料ですが、それだけで証憑の税務上の適否や申請の扱いを決めるものではありません。会社の規程と証憑の種類・記載事項をそろえて、人が判断します。

候補から照合へ進むときの確認順

候補抽出を実務の確認へつなぐなら、戻り値をそのまま承認フラグへ変換せず、次の順で情報をそろえます。

  1. 候補が証憑のどの欄にあるか(合計・明細・税額など)を特定する。
  2. 通貨、符号、税込・税抜を申請の金額とそろえる。
  3. 10%・8%など税率別の対価と税額、申請の税区分を対応させる。
  4. 1枚の証憑を複数明細へ按分していないか、複数証憑を1申請へまとめていないかを確認する。
  5. 対応関係と会社の規程を人が確認するまで、候補あり・候補なし・未判定を自動承認へ変換しない。

AI-OCRは文字を読む処理、人は照合の意味を判断する

テキスト抽出とOCRは、文字を得る入口として使い分けられます。ただし、どちらの方法で文字を得ても、数字の部分一致だけでは欄の意味を決められません。AI-OCRを使う場合も、読み取った文字列は照合候補として扱い、証憑と申請の対応を確認する前に自動OKへ進めません。

処理得られるもの止める地点
テキスト抽出ページから取得した文字列数字の候補があっても、欄・通貨・符号は未確定
OCR画像から読み取った文字列(使う場合)読み取り後も、欄・税・按分・証憑の対応は未確認
人と会社の規程証憑と申請の対応、税区分、按分理由の確認個別条件に基づいて承認または差し戻しを判断

この線引きなら、関数が True を返したときも、その意味は「候補あり」にとどまります。False は候補なし、None はこの関数では未判定です。どの結果でも、証憑の欄・金額の意味・税率・按分・複数証憑との対応が確認できるまでは、機械の戻り値だけを照合済みの根拠にしません。

連載「経理をAIに任せる」の関連記事

  1. 経理AIの内部統制をどう設計する?作業・責任・監査証跡の分け方
  2. 新規取引先チェックをAIで自動化|閉鎖・合併済み法人を検知した方法
  3. 経費精算のレシート照合で部分一致を使う限界|AI-OCRなどの候補抽出と人の確認(この記事)
  4. 会計ソフトのAPI比較|AI経理で確認したい4つの選定基準

会計ソフトのAPI比較へ進む場合も、先に確認したいのは候補抽出と照合の境界です。候補の文字列が取れることだけで、APIの仕様、個別証憑の税務処理、承認までが自動化できるとは判断しません。

AI会計ソフトのAPI比較|AI経理で確認したい4つの選定基準会計APIは「公開されているか」だけでは選べません。実業務を一件決め、対象データと操作、認証・権限、契約…