新規取引先チェックをAIで自動化|合併で閉鎖された法人番号を見つけた方法

読了 約13分 たびすけ
AIによる新規取引先チェックと企業照合を表す記事のアイキャッチ

13桁の法人番号は、計算式だけで真偽の半分が分かります。先頭のチェックディジットを検算すれば、番号が形式どおりかどうかは手元で確かめられるからです。ただ、残りの半分は計算では分かりません。その番号の法人が実在して、いまも閉鎖されずに生きているか、という半分です。

この「残り半分」で引っかかった申請が、実際にありました。新規取引先の登録申請をAIにチェックさせていたときのことです。申請された商号は登記上の商号と一致しています。それなのに、入力されていた法人番号は合併で閉鎖された法人のもので、存続会社には別の法人番号がありました。商号だけを見ていたら、そのまま通していたかもしれません。

この不一致を見つけたのはAIです。バクラクの申請内容と国税庁の情報を照合させて、法人番号の形式を確かめたうえで、商号、インボイス番号、閉鎖年月日、承継先を調べ、問題のある申請だけを「要確認」に振り分けてもらいました。

といっても、会社が実在するかどうかをAIの知識だけで推測させたわけではありません。法人番号は計算式で検算し、会社情報は国税庁の公式Web-APIから取得します。そして、最後に承認するか差し戻すかを決めるのは人です。

商号が合っていても、法人番号は閉鎖済みかもしれない

13桁の法人番号はチェックディジットで検算し、商号、閉鎖年月日、承継先は国税庁の法人番号Web-APIで照合する。AIの仕事は全件をOKと要確認に分けるところまで。人が判断するのは、要確認になった申請だけ。

連載「経理をAIに任せる」(全4回)

  1. 経理AIの内部統制はどう設計する?作業と責任、証跡の分け方
  2. 新規取引先チェックをAIで自動化|合併で閉鎖された法人番号を見つけた方法(この記事)
  3. 経費精算のレシートと明細を、AIに全部突合させてみた
  4. 会計ソフトのAPI比較|AI経理で確認したい4つの選定基準

この実例でAIに任せたのは、公式情報を集めて不一致を見つけるところまでです。引っかかった申請をどう扱うかは、人が根拠を見て決めました。

新規取引先チェックで見ている4つの項目

そもそも新規取引先の登録申請で私が確認しているのは、少なくとも次の4つです。

  • 法人が実在するか
  • 申請された商号と登記上の商号が一致するか
  • インボイス登録番号と法人番号が整合するか
  • 法人番号が正しい形式で、その法人が閉鎖または合併済みではないか

13桁がそろって見えても、入力ミスはあり得ます。それに今回のように、商号は合っているのに中の番号だけが古い、というケースも実際にあるわけです。だから、番号の形式を検算する工程と、公式情報と照合する工程を分けることにしました。

申請の取得と、法人番号13桁の検算

この工程で確かめるのは、番号の形式だけです。流れの最初は、バクラクから申請中の新規取引先申請を取ってくるところ。ベンダーAPIに関わる部分は、公開できる範囲に絞った擬似コードにしています。

# バクラクAPIで「申請中の新規取引先申請」を取得し、
# 各申請から 法人番号・取引先名・インボイス番号 を取り出す
applications = bakuraku.fetch_requests(status="IN_PROGRESS", form="新規取引先申請")

取り出した法人番号は、まず手元で検算します。法人番号は、12桁の基礎番号の前にチェックディジット1桁を付けた13桁で、計算方法は国税庁が公開しています。つまり、番号が形式どおりかどうかは、APIに聞くまでもなく確かめられます

def validate_corporate_number(number: str) -> bool:
    """法人番号13桁のチェックディジットを検証する。"""
    if not number or len(number) != 13 or not number.isdigit():
        return False
    check_digit = int(number[0])
    base = number[1:]                      # 下位12桁が基礎番号
    total = 0
    for i, ch in enumerate(reversed(base)):
        weight = 2 if i % 2 == 1 else 1    # 右から 奇数桁=1, 偶数桁=2
        total += int(ch) * weight
    return (9 - (total % 9)) == check_digit

ただ、この検算で分かるのは「番号が計算式に合っているか」だけなんです。その法人が実在するのか、いまも同じ番号が有効なのかは、ここでは何も分かりません。

実在と閉鎖状態は、国税庁の法人番号Web-APIで照合する

法人の実在と商号、閉鎖状態は、国税庁の法人番号Web-APIに問い合わせます。Web-API Ver.4はCSV(Unicode)を指定して法人情報を取得でき、利用には無料で発行されるアプリIDが必要です。

import csv, requests

NTA_API = "https://api.houjin-bangou.nta.go.jp"

def nta_lookup(numbers: list[str], app_id: str, version: str = "4") -> dict:
    """国税庁 法人番号Web-APIで 実在・商号・閉鎖 を照会する。"""
    resp = requests.get(
        f"{NTA_API}/{version}/num",
        params={
            "id": app_id,                 # 無料発行のアプリID
            "number": ",".join(numbers),  # 最大10件まとめて
            "type": "02",                 # CSV(Unicode)で受け取る
            "history": "0",               # 最新の登記のみ
        },
    )
    resp.raise_for_status()
    records = {}
    for row in csv.reader(resp.content.decode("utf-8").splitlines()):
        if len(row) <= 20:
            continue                      # ヘッダー行などをskip
        records[row[1]] = {               # row[1] = 法人番号
            "name":      row[6],          # 商号
            "closeDate": row[18],         # 閉鎖年月日(合併・解散など)
            "successor": row[20],         # 承継先の法人番号
        }
    return records

今回の件で効いたのは、このうち閉鎖年月日と承継先の項目でした。商号が一致するかだけでなく、「その番号がいまも生きているか」まで同じ照会で確認できます。あとは取得した情報と申請内容を比べて、問題がなければOK、条件に1つでも当てはまれば要確認、という判定にしました。

issues = []
if not validate_corporate_number(corp):
    issues.append("法人番号CD不正")
if invoice and invoice != "T" + corp:
    issues.append("インボイス番号不整合")
if record is None:
    issues.append("国税庁に該当なし")
elif record["closeDate"]:                       # ← ここが効いた
    issues.append("閉鎖/合併(承継先: " + (record["successor"] or "不明") + ")")
elif normalize(client_name) != normalize(record["name"]):
    issues.append("商号不一致")

verdict = "要確認" if issues else "OK"

見てのとおり、法人番号や会社情報をAIの知識で判定させている箇所はありません。判定材料は、計算結果とAPIのレスポンスだけです。それでも、商号の表記をそろえる方法や例外条件が間違っていれば、誤判定は起きます。だから、コードが動いたかどうかだけでなく、出てきた判定結果まで確かめるようにしています。

商号は合っていた。それでも閉鎖年月日が返ってきた

で、冒頭の申請です。APIの応答には閉鎖年月日が入っていて、合併後の承継先として別の法人番号も返ってきました。申請された商号と登記上の商号を見比べても、違和感はどこにもありません。番号の中身だけが古かったわけです。

この申請は自動承認せず、閉鎖年月日と承継先を根拠に「要確認」で止めました。申請者へ差し戻したのは私です。AIに任せたのは、全件を同じ条件で調べて、人が見るべき申請を絞り込むところまででした。

アプリIDがない間はWeb検索で暫定確認

国税庁のWeb-APIには、1つ前提があります。無料とはいえ、アプリIDが発行されるまでは使えないことです。発行を待つ間など、すぐにAPIを使えない場合のために、法人番号データベースの houjin.info やhoujin.jpをAIに検索させて、商号や所在地、閉鎖状態、承継先を確認する手順も用意しました。

  • 番号から商号、所在地、登記状態、承継先を確認する
  • 申請された商号や住所と照合し、不一致があれば「要確認」にする
  • 参照URLと確認日を残し、番号をAIに生成させない
  • 重要な判断は国税庁の公表サイトまたは公式APIで再確認する

この手順はプロジェクトのREADMEに置いて、AIが作業を始めるときに読める場所にしています。とはいえ、第三者サイトの更新時点や表示内容が、公式情報と同じとは限りません。なので位置づけは、あくまで暫定確認と根拠集めです。APIの代わりにずっと使うつもりはありません。

照合コードもAIに書いてもらった

ここまで載せたコードを、私がすべて手で書いたわけではありません。確認したい項目と正常例、異常例、出力形式をAIに渡して、初稿と修正案を作ってもらいました。私がやったのは、チェックディジットの計算式とAPIのレスポンス、実際の判定結果を確かめることです。

実装を一から書けない私でも、業務の要件を説明して結果を確かめることで、こういう確認ツールを作れるようになりました。もっとも、コードが動いたからといって、そのまま会社の承認ルールに使えるとは限りません。

同じ考え方は経費精算にも広げていて、レシートと申請明細は全件突合し、画像しかない証憑だけをAI-OCRに回しています。

経理経費精算のレシート照合をAI-OCRで自動化|分割計上も検知経費精算のレシートと申請明細を全件突合しました。文字を取り出せるPDFはPythonで照合し、画像だけを…

振り返ると、商号が合っていた閉鎖法人の申請を止められたのは、AIの推測が当たったからではありません。13桁の検算と国税庁の情報を根拠に「要確認」で止め、最後は人が差し戻すという流れを、先に作っておいたからです。

実装時に確認した公式情報