diff --git a/freee-receipt-upload.py b/freee-receipt-upload.py index 2566abd..0085ddc 100644 --- a/freee-receipt-upload.py +++ b/freee-receipt-upload.py @@ -4,6 +4,8 @@ import configparser import json from get_freee_token import get_current_token, refresh_token +import re +from pprint import pprint # 処理対象のディレクトリを指定 @@ -28,7 +30,6 @@ print(f"処理中のファイル: {filename}") print(f"Batch ID: {batch_id}") - # 認証確認のためのデバッグ出力を追加 try: message_batch = client.beta.messages.batches.retrieve( batch_id, @@ -53,10 +54,8 @@ match result.result.type: case "succeeded": print(f"成功! {result.custom_id} {batch_id}") - print(result.result.message.content[0].text) case "errored": if result.result.error.type == "invalid_request": - # リクエスト本文を修正してから再送信する必要があります print(f"バリデーションエラー {result.custom_id} {batch_id}") print("リクエスト本文を修正してから再送信する必要があります") exit(1) @@ -77,33 +76,34 @@ "document_type": "receipt", } - # message_batchからの内容更新は維持 for result in client.beta.messages.batches.results(batch_id): if result.result.type == "succeeded": message_text = result.result.message.content[0].text - if message_text.startswith(''): - message_text = message_text.replace('', '').replace('', '').strip() - + match = re.search(r'(.*?)', message_text, re.DOTALL) + if match: + message_text = match.group(1).strip() + message_content = json.loads(message_text) message_content['description'] = message_content['description'][:255] - if 'qualified_invoice' in message_content: - invoice_num = message_content['qualified_invoice'] + if ('invoice_registration_number' in message_content and + message_content.get('receipt_metadatum_partner_name') == 'unknown'): + invoice_num = message_content['invoice_registration_number'] if invoice_num.startswith('T') and len(invoice_num) == 14: try: sel_reg_no = invoice_num[1:] url = f"https://www.invoice-kohyo.nta.go.jp/regno-search/detail?selRegNo={sel_reg_no}" response = requests.get(url) - from bs4 import BeautifulSoup soup = BeautifulSoup(response.text, 'html.parser') - - company_name = soup.select_one('p.itemdata.sp_nmTsuushou_data') - if company_name: - message_content['partner_name'] = company_name.text.strip() + real_partner_name = soup.select_one('p.itemdata.sp_nmTsuushou_data') + if real_partner_name: + message_content['receipt_metadatum_partner_name'] = real_partner_name.text.strip() except Exception as e: - print(f"Error fetching company name: {e}") + print(f"Error fetching partner_name: {e}") + del message_content['invoice_registration_number'] + pprint(message_content, indent=2, width=80) new_content = {k: v for k, v in message_content.items() if k not in payload} payload.update(new_content) diff --git a/get_freee_token.py b/get_freee_token.py index f5814a4..936cf97 100644 --- a/get_freee_token.py +++ b/get_freee_token.py @@ -13,6 +13,11 @@ CODE = config['freee']['code'] TOKEN_FILE = "freee_tokens.json" +HEADERS = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'cache-control': 'no-cache' +} + def save_tokens(access_token, refresh_token): """トークンをJSONファイルに保存""" tokens = { @@ -20,7 +25,7 @@ def save_tokens(access_token, refresh_token): "refresh_token": refresh_token } with open(TOKEN_FILE, 'w') as f: - json.dump(tokens, f) + json.dump(tokens, f, indent=4) def load_tokens(): """保存されたトークンを読み込み""" @@ -31,11 +36,6 @@ def load_tokens(): def get_access_token(): """初期認可コードを使用してアクセストークンを取得""" - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'cache-control': 'no-cache' - } - data = { 'grant_type': 'authorization_code', 'redirect_uri': REDIRECT_URI, @@ -44,21 +44,15 @@ def get_access_token(): 'code': CODE } - response = requests.post(TOKEN_URL, headers=headers, data=data) + response = requests.post(TOKEN_URL, headers=HEADERS, data=data) if response.status_code == 200: tokens = response.json() save_tokens(tokens['access_token'], tokens['refresh_token']) return tokens - else: - raise Exception(f"トークン取得エラー: {response.text}") + raise Exception(f"APIエラー: {response.text}") def refresh_access_token(refresh_token): """リフレッシュトークンを使用して新しいアクセストークンを取得""" - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'cache-control': 'no-cache' - } - data = { 'grant_type': 'refresh_token', 'redirect_uri': REDIRECT_URI, @@ -67,54 +61,38 @@ def refresh_access_token(refresh_token): 'refresh_token': refresh_token } - response = requests.post(TOKEN_URL, headers=headers, data=data) + response = requests.post(TOKEN_URL, headers=HEADERS, data=data) if response.status_code == 200: tokens = response.json() save_tokens(tokens['access_token'], tokens['refresh_token']) return tokens - else: - raise Exception(f"トークン更新エラー: {response.text}") + raise Exception(f"APIエラー: {response.text}") def get_current_token(): """現在の有効なトークンを取得する関数""" saved_tokens = load_tokens() - if not saved_tokens['refresh_token']: - raise Exception("トークンが存在しません。初回認証を行ってください。") - - try: - # 一度リフレッシュを試みる - new_tokens = refresh_access_token(saved_tokens['refresh_token']) - return new_tokens['access_token'] - except Exception as e: - raise Exception(f"トークン更新に失敗しました: {e}") + raise Exception("トークンが存在しません") + return refresh_access_token(saved_tokens['refresh_token'])['access_token'] def refresh_token(): """外部から呼び出し可能なリフレッシュ関数""" saved_tokens = load_tokens() if not saved_tokens['refresh_token']: - raise Exception("リフレッシュトークンが存在しません") - + raise Exception("トークンが存在しません") return refresh_access_token(saved_tokens['refresh_token']) def main(): - # 保存されたトークンを読み込む saved_tokens = load_tokens() - - if saved_tokens['refresh_token']: - # リフレッシュトークンが存在する場合は、それを使用してトークンを更新 - try: - new_tokens = refresh_access_token(saved_tokens['refresh_token']) + try: + if saved_tokens['refresh_token']: + refresh_access_token(saved_tokens['refresh_token']) print("トークン更新成功") - except Exception as e: - print(f"トークン更新エラー: {e}") - else: - # リフレッシュトークンが存在しない場合は、新規にアクセストークンを取得 - try: - tokens = get_access_token() + else: + get_access_token() print("初回トークン取得成功") - except Exception as e: - print(f"トークン取得エラー: {e}") + except Exception as e: + print(f"エラー: {e}") if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/main.py b/main.py index 5e5ee25..1ec35ca 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ from io import BytesIO from resize import resize_image import configparser +import sys # 処理対象のディレクトリを指定 source_dir = "./images" @@ -91,8 +92,17 @@ def process_image(image_path, is_first=False): message = client.beta.prompt_caching.messages.create(**message_params) print(message.content) -jpg_files = glob.glob(os.path.join(source_dir, "*.jpg")) -for i, jpg_file in enumerate(jpg_files): - print(f"Processing {jpg_file}...") - process_image(jpg_file, is_first=(i==1)) - print("-" * 80) +if len(sys.argv) > 1: + jpg_file = os.path.join(source_dir, sys.argv[1]) + if os.path.exists(jpg_file): + print(f"Processing {jpg_file}...") + process_image(jpg_file) + print("-" * 80) + else: + print(f"エラー: ファイル {jpg_file} が見つかりません") +else: + jpg_files = glob.glob(os.path.join(source_dir, "*.jpg")) + for i, jpg_file in enumerate(jpg_files): + print(f"Processing {jpg_file}...") + process_image(jpg_file, is_first=(i==1)) + print("-" * 80) diff --git a/prompt.txt b/prompt.txt index cb2a206..95865fa 100644 --- a/prompt.txt +++ b/prompt.txt @@ -1 +1,114 @@ -You are an AI assistant tasked with analyzing receipt images using OCR (Optical Character Recognition) technology and extracting specific information. Your goal is to process the given receipt image and output the extracted data in a structured JSON format.\n\nUsing your OCR capabilities, carefully examine the receipt image and extract the following information. Present your findings in a JSON format with the following fields:\n\n1. receipt_metadatum_partner_name: The name of the issuing entity (発行元)\n2. receipt_metadatum_issue_date: The date of issue in yyyy-mm-dd format (発行日)\n3. receipt_metadatum_amount: The total amount on the receipt\n4. qualified_invoice: Determine if the receipt is a qualified invoice\n5. description: A description of the receipt, including what was purchased (200 characters max)\n\nFor the \"qualified_invoice\" field, follow these rules:\n- 発行者名が株式会社セブンイレブン 東京店などと書かれている場合、\"セブンイレブン\"とだけ省略するように\n- If you find a string matching the pattern T[1-9][0-9]{12}$ (T1000000000001) on the receipt, set the value to \"qualified\"\n- If you cannot determine or the pattern is not found, set the value to \"unselected\"\n\nAfter processing the image, present your findings in the following JSON format:\n\n\n{\n \"receipt_metadatum_partner_name\": \"セブンイレブン\",\n \"receipt_metadatum_issue_date\": \"yyyy-mm-dd\",\n \"receipt_metadatum_amount\": 1,\n \"qualified_invoice\": \"qualified\" or \"unselected\",\n \"description\": \"Describe description of receipt purchase\"\n}\n\n\nEnsure that all fields are filled out based on the information you can extract from the receipt image. If you cannot determine a value for a particular field, use \"unknown\" as the value, except for the \"qualified_invoice\" field, which should be \"unselected\" if undetermined.\nAlso, if the amount is unknown, please put 99999 in the value.\nThis is a Japanese receipt, so please write in Japanese. \ No newline at end of file +あなたはOCR(光学文字認識)技術を使用してレシート画像を分析し、特定の情報を抽出するAIアシスタントです。与えられたレシート画像を処理し、抽出したデータを構造化されたJSON形式で出力することが目標です。 + +OCR機能を使用してレシート画像を注意深く確認し、以下の情報を抽出してください。以下のフィールドを持つJSON形式で結果を提示してください: + +1. receipt_metadatum_partner_name: レシート発行者の名称 +2. receipt_metadatum_issue_date: 発行日(yyyy-mm-dd形式) +3. receipt_metadatum_amount: レシートの合計金額 +4. invoice_registration_number: T[1-9][0-9]{12}$のパターンに一致する文字列(例:T1000000000001) +5. qualified_invoice: 適格請求書かどうかの判定 +6. description: 購入品目の説明(255文字以内) + +「invoice_registration_number」フィールドについては、以下のルールに従ってください: +- レシート上でT[1-9][0-9]{12}$のパターン(例:T1000000000001)に一致する文字列が見つかった場合、その文字列を設定 +- パターンが見つからないまたは判断できない場合は「unknown」を設定 + +「qualified_invoice」フィールドについては、以下のルールに従ってください: +- invoice_registration_numberがT[1-9][0-9]{12}$に一致する場合は「qualified」を設定 +- invoice_registration_numberが「unknown」の場合は「unselected」を設定 + +「description」フィールドについては、以下のルールに従ってください: +- 実際に購入した商品のカテゴリー、商品名、数量、金額、取引先電話番号などを含めてください + + +「receipt_metadatum_partner_name」フィールドについては、以下のルールに従ってください: +- 発行者名が「株式会社セブンイレブン 東京店」のように記載されている場合は、「セブンイレブン」のように省略してください +- 以下が発行者名のヒントです。ヒットしたらその発行者名を設定してください。ヒットしない場合は「unknown」を設定してください。 + - ドン・キホーテ + - EneJet + - 水道飯塚 + - JetBrain + - maruzenジュンク堂書店 + - 無印良品 + - 飯塚信用金庫 + - 東和防災システム + - ゆうびん + - JA + - 川食 + - ミニストップ + - ダイレックス + - コカ・コーラボトラーズジャパン + - コスモ電材 + - ネクスコ西日本 + - パソコン工房 + - NTT + - 宗像交通タクシー + - 宗像平和タクシー + - Mirantis + - ゆめタウン + - ゆめマート + - 株式会社ドラッグストアモリ + - ぎょうざの山八 + - プロの料理村 + - BOOK OFF + - 市場バリュー + - ドラッグコーエイ + - パッケージプラザ + - 株式会社トーホー + - ヤスタケ薬局 + - 日本めん株式会社 + - WASHハウス + - サンドラッグ + - ナフコ + - きみかど + - 東屋 + - ルミエール + - イオン + - セブンミート食品 + - ファディ + - コスモス + - 綜合開発企業組合 + - 中村産業株式会社 + - 大内田産業株式会社 + - 有限会社マルイチ商会 + - 株式会社エルゼ + - ワークマン + - ローソン + - ループ + - ヤマダデンキ + - モノタロウ + - メルカリ + - ハローデイ + - ダイソー + - セリア + - セブンイレブン + - セカンドストリート + - コメリ + - ケーズ電気 + - オーリック + - TRIAL + - PCデポ + - NHK + - エディオン + - 西日本新聞 + - サンレックス + + +画像処理後、以下のJSON形式で結果を提示してください: + + +{ +"receipt_metadatum_partner_name": "セブンイレブン" or "unknown", +"receipt_metadatum_issue_date": "yyyy-mm-dd", +"receipt_metadatum_amount": 99999, +"invoice_registration_number": "T[1-9][0-9]{12}$(例:T1000000000001)" または "unknown", +"qualified_invoice": "qualified" または "unselected", +"description": "レシートの購入内容の説明" +} + + +発行者名が「株式会社セブンイレブン 東京店」のように記載されている場合は、「セブンイレブン」のように省略してください +レシート画像から抽出できる情報に基づいて、すべてのフィールドを入力してください。特定のフィールドの値が判断できない場合は、「qualified_invoice」フィールドを除いて「unknown」を値として使用してください。「qualified_invoice」フィールドは、判断できない場合は「unselected」としてください +また、金額が不明な場合は、値に99999を設定してください +注釈は不要です +これは日本のレシートなので、日本語で記述してください diff --git a/resize.py b/resize.py index bf4dbb6..67d59ae 100644 --- a/resize.py +++ b/resize.py @@ -6,13 +6,9 @@ def resize_image(img): """ width, height = img.size - # 条件1: 1.15メガピクセル以上かつ両方の寸法が1568px以上 condition1 = (width * height >= 1150000) and (width >= 1568 and height >= 1568) - - # 条件2: (幅 * 高さ) / 750 が1000以上 condition2 = (width * height) / 750 >= 1000 - # いずれかの条件に該当する場合、リサイズを実行 if condition1 or condition2: if condition1: ratio = 1568 / max(width, height) @@ -22,8 +18,6 @@ def resize_image(img): new_width = int(width * ratio) new_height = int(height * ratio) - # リサイズを実行して返す return img.resize((new_width, new_height), Image.Resampling.LANCZOS) - # リサイズが不要な場合は元の画像をそのまま返す return img \ No newline at end of file diff --git a/view.py b/view.py index 582ce09..2b5d94c 100644 --- a/view.py +++ b/view.py @@ -2,7 +2,6 @@ import configparser import sys -# 処理対象のディレクトリを指定 target_dir = "./batched" config = configparser.ConfigParser() @@ -12,7 +11,6 @@ api_key=config['anthropic']['api_key'], ) -# コマンドライン引数を取得 if len(sys.argv) < 2: print("使用方法: python view.py <バッチID>") sys.exit(1) @@ -31,7 +29,6 @@ batch_id, ) -# メモリ効率の良いチャンクで結果ファイルをストリーミングし、1つずつ処理 for result in client.beta.messages.batches.results( batch_id, ): @@ -41,10 +38,8 @@ #print(result) case "errored": if result.result.error.type == "invalid_request": - # リクエスト本文を修正してから再送信する必要があります print(f"バリデーションエラー {result.custom_id}") else: - # リクエストを直接再試行できます print(f"サーバーエラー {result.custom_id}") case "expired": print(f"リクエストの有効期限切れ {result.custom_id}")