📚

電子取引保存法の面倒な事務作業を自動化する

2024/03/06に公開

背景

電子取引保存法に対応しなければいけないが、正直面倒なので手間を減らしたい

作ったもの

  • input: レシート画像をNotionDBにアップロードする

  • output: レシートの画像に基づいて他のプロパティが自動で追加される

構成

主要なプロセスを時系列順に示したものです。【】内は対応するツールやAPIです。

  1. 【make】NotinoDBにアイテムが追加されたことをトリガーに、以下の処理を行うFunctionを呼ぶ
  2. クエリに含まれたレシート画像のURLか画像データを取得する
  3. 【Google Document AI】OCR(画像から文字を読み取る)する
  4. 【openAI API】: 抽出した文字列をJSONデータにする
  5. 【NotionAPI】: NotionDBに書き込む

FunctionはGoogle Cloud Functionsにデプロイしています。Cloud Functionsについては、プラットフォーム特有の知識が要り時間を食ってしまいました。

各工程の解説

続いて工程ごとの詳しい解説をしていきます。

1/ 【make】NotionDBへのアイテム追加をトリガーにする

NotionのAPIはWEBHookに非対応なので、外部連携ツールを使います。Zapierなどが有名ですが、今回はコストメリットのあるmakeを使います。makeは今まで馴染みがありませんでしたがノーコードツールとして打ち出しており、直感的なUIが印象的です

Notionとの連携です。

ちなみにMakeではトリガーの発火は①アイテムの更新、或いは②アイテムの作成、どちらかを選べます。今回は②を選択しています。
呼び出したFunctionがNotinoDBのアイテムを更新することになるので、その更新がトリガーとなりmakeがFunctionを呼び出すという無限ループが起きてしまいます。アイテム作成をトリガーとすることでこれを回避しています。

Notionとmakeの連携における注意点

Read user information icluding email addressにチェックが必要です。連携する際にエラーが出て、原因がよくわからず困りました。

続く、Httpリクエストの設定です。NotionDBから取得した任意のプロパティをクエリに含めることが可能です。UIから選ぶだけなので簡単です。


今回は画像のurlとDBアイテムのIDをクエリパラメーターとして渡します。

2/ 画像データの取得

クエリパラメータとして画像のurlが渡されているので、バイナリデータを取得します。特筆することはありませんが、標準搭載されたfetch()を使うことでおおよそ次のように取得できます。

    //Cloud functionではこのようにクエリパラメータを取得します
    const imageUrl = req.query["imageUrl"]

    const response = await fetch(imageUrl);
    const arrayBuffer = await response.arrayBuffer();
    const buffer = Buffer.from(arrayBuffer);

コードはこちらの記事を参考にしています。

3/ 【Document AI】によるOCR

画像はDocument AIをコンソール画面から試したものです。レシートがかなりくしゃくしゃな状態ですが、それでも正確に読み取れていることがわかります。

実際に抽出されたテキストはこんな感じになります。

STARBUCKS 札幌北野店 #1431 TEL 011-888-0035 1 S 7x th マグ 301 Starbucks eGift適用 本体合計(1点) (10%対象 総合計 455 (カスタム) 455 455 消費税 45) 500 Starbucks eGift (つり銭なし 500 釣り 0 「軽」印は軽減税率(8%)適用商品 For Here 030162350103 4068 2024/02/20 14:21:27 スターバックスコーヒージャパン株式会社 登録番号 7011001031943 発行日: 02/20 二次元コードを読み込んで ひと足早いお花見体験、 満開の桜と 踊るベアリスタAR #スターバックスさくら 2024 https://sbux.jp/sakuraa2024_receipt 毎月20日は ETHICALLY CONNECTING DAY エシカルなコーヒーの日 公式サイトでブラックエプロン バリスタの コーヒーストーリー ゼロ

OCRに用いるAPI、2種の使い分け

ちなみにGCPで利用できる、OCR向けのAPIには次の2種類があります。

  • Document AI
  • Vison API

以下の記事によると、それぞれ得意不得意があるようです。適切に使い分けることで、読み取り精度の向上が期待できます。
https://dev.classmethod.jp/articles/using-document-ai-and-google-cloud-vision-api-for-ocr/

3/ openAIによるデータの構造化

続いてOCRで読み取ったテキストから必要な情報を抽出して、JSONデータにします。抽出してほしい情報やどのようなJSONデータで返してほしいかについては、APIを呼び出す際にプロンプトとして渡します。

画像はコンソールから試したものですが、テキストからいい感じに推論・抽出してくれます。よく見るとjson以外の余計な応答も混じっていますが、Jsonを返すだけのjsonmodeに設定することで解決します。
https://platform.openai.com/docs/guides/text-generation/json-mode

ちなみにこちらの記事のいい使い方だなと思ったので、それを真似しています。
https://qiita.com/watanabe-tsubasa/items/12dc7ba9a6de55e8afd9

2種のOpenAI API、両者の違いとか

  • Assistants API: 過去行ったやり取りを踏まえた応答が得られる。実装がちょっと面倒
  • Completion API: シンプルで手軽に呼び出しできる

それぞれの特徴について、両方試した上で自分はざっくりこのように理解しています。(間違ってたらごめんなさい🙇)
リリースされて間も無くまだbeta版のAssistansAPIですが、スレッド上にやり取りが累積すればするだけ、応答の生成にお金がかかってしまうという仕様があります。これのおかげで、ちょっと使いにくさがあります。
対するCompletion APIはお手軽。とにかくシンプルに呼び出しでき、コストもかかりにくい点に優位性があります。
https://platform.openai.com/docs/api-reference/making-requests

今回は渡された文字列をいい感じにjsonに変換してもらうだけなので、completion APIを使っています。

4/ NotionDBに各種プロパティを書き込む

NotionAPIを使用します。こちらも公式のsdkを使うと楽です。
こちらのドキュメントを見て実装しています。データ構造が少々特殊なこと以外、特筆することはないです。該当部分のJSコードです。

    const properties = {
        Summary: {
            title: [
              {
                text: {
                  content: obj.summary,
                },
              },
            ],
          },
        Accounts: {
            select: {
                name: obj.accounts,
            },
        },
        Supplier: {
            rich_text: [
              {
                text: {
                  content: obj.supplier,
                },
              },
            ],
        },
        Price: {
            number: obj.price
        },
        Date: {
          date: {
            start: obj.date 
          }
        }
    };

https://developers.notion.com/docs/working-with-databases#adding-pages-to-a-database

所感

今回は個人的に愛用しているのもあってNotionDBを使用していますが、Zapierや今回活用したmakeなどの外部連携ツールさえ対応していれば、同じ要領で自動化が可能です。
割とAIを活用していて、楽しく開発ができました。

余談

元々はこんな感じでLINEを画像アップロードのインターフェイスにしていました。

しかし現在のNotionAPIの仕様ではNotionDBに画像ファイルを追加できないことがわかりました。電子取引保存法ではレシートの画像そのものも保存しておく必要があるため、直接NotionDBにファイルを突っ込んでそれをトリガーにFunctionを呼び出す今の構成に落ち着きました。

Discussion