From 3d9f82f1ea3ff2f90f2422dabe42bde5c30b0812 Mon Sep 17 00:00:00 2001 From: Bryce Date: Wed, 27 May 2026 09:21:06 -0700 Subject: [PATCH] progress --- code/main.py | 89 ++++++++++++++++++++++++++----------------------- tests/4681.pdf | Bin 0 -> 24569 bytes 2 files changed, 48 insertions(+), 41 deletions(-) create mode 100644 tests/4681.pdf diff --git a/code/main.py b/code/main.py index 34fdf4c..06ee0b3 100755 --- a/code/main.py +++ b/code/main.py @@ -1,69 +1,76 @@ - #!/usr/bin/env python3 +import base64 import json import os -import pprint import urllib.request import openai -openai.api_key = "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" +client = openai.OpenAI(api_key="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") def slurp_file(filename): with open(filename, 'r') as file: - data = file.read() - return data + return file.read() -BASE_PROMPT="""You extract invoice details from pdfs. Some pdfs are invoices, some are credits, and some are statements that may contain statements or credits. Numbers in parenthesis typically indicate credits. Always follow this json schema. Do not respond with anything except the raw json response. Do not respond in code blocks(```). If you don't find any invoices, make sure to fill out the explanation field at least. -``` -{} -``` -""".format(slurp_file(os.path.join(os.path.dirname(__file__), 'schema.json'))) +BASE_PROMPT="""You are an invoice extraction assistant. Your job is to read PDF documents and extract all invoice and credit note details into structured JSON. +DOCUMENT TYPES YOU MAY ENCOUNTER: +1. **Single Invoice** — one invoice with line items, totals, dates, etc. +2. **Credit Note** — similar to an invoice but represents a credit/refund. Extract it the same way; the total will be positive (the credit amount). +3. **Statement / Summary** — a document listing multiple invoices or credits in a table or list format. Each row or entry represents a separate invoice/credit. Extract EACH one as a separate object in the output array. +4. **Mixed** — a document containing both invoices and credits. -client = openai.OpenAI(api_key="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") -client.api_key = "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" +EXTRACTION RULES: +- Extract EVERY invoice or credit you can find. If the document is a statement listing 10 invoices, return all 10. +- **customer_identifier**: The name of the customer/buyer. Look for "Bill To", "Customer", "Sold To", or the company name at the top. +- **vendor_identifier**: The name of the vendor/seller. Look for "From", "Vendor", "Supplier", letterhead, or the company issuing the document. +- **date**: The invoice date in ISO 8601 format (YYYY-MM-DD). If multiple dates exist, use the invoice date, not the due date or statement period. +- **invoice_number**: The unique invoice or credit note number. Look for labels like "Invoice #", "Inv No", "Credit Note #", "Reference", "Doc #". +- **account_number**: The customer's account number if present. Not required — omit if not found. +- **total**: The total amount as a decimal string (e.g., "1234.56"). Use the grand total or amount due. For credits, use the credit amount as a positive number. Numbers in parentheses indicate credits — extract them as positive values. +- **explanation**: Only use this when you cannot find any valid invoices. Provide a detailed reason (e.g., "document is blank", "PDF contains only images with no extractable text", "document is a cover letter with no invoice data"). + +IMPORTANT: +- Do NOT skip entries because some fields are missing. Extract what you can. +- For statements/summaries, each row in an invoice table is a separate invoice entry. +- If OCR fails completely and no text can be extracted at all, return an array with one object containing only the explanation field. +- Your FINAL response to the user must be ONLY a JSON array. Do NOT wrap it in markdown code blocks. Do NOT add any prose before or after the JSON.""" def analyze_pdf(pdf_path): - assistant = client.beta.assistants.create( - name="pdf-reader", - instructions=BASE_PROMPT, - model="gpt-4o", - tools=[{"type": "file_search"}], - ) - with open(pdf_path, 'rb') as f: - message_file = client.files.create(file=f, purpose="assistants") + pdf_data = f.read() - thread = client.beta.threads.create( - messages=[ + base64_string = base64.b64encode(pdf_data).decode("utf-8") + + response = client.responses.create( + model="gpt-4o", + instructions=BASE_PROMPT, + input=[ { "role": "user", - "content": "extract the invoice(s) and/or credit(s) details from this invoice or statement", - "attachments": [ - {"file_id": message_file.id, "tools": [{"type": "file_search"}]} + "content": [ + { + "type": "input_file", + "filename": os.path.basename(pdf_path), + "file_data": f"data:application/pdf;base64,{base64_string}", + }, + { + "type": "input_text", + "text": "extract the invoice(s) and/or credit(s) details from this document.", + }, ], } - ] + ], ) - print(thread.id) - - run = client.beta.threads.runs.create_and_poll( - thread_id=thread.id, assistant_id=assistant.id - ) - - messages = list(client.beta.threads.messages.list(thread_id=thread.id, run_id=run.id)) - print("MESSAGES") - pprint.pprint(messages) - print("\n\n") - print("status", run.status) - print("\n\n") - print("full run") - pprint.pprint(run) - return json.loads(messages[0].content[0].text.value) + text = response.output_text + import re + match = re.search(r'```(?:json)?\s*\n(.*?)\n```', text, re.DOTALL) + if match: + text = match.group(1) + return text def analyze_url(url): diff --git a/tests/4681.pdf b/tests/4681.pdf new file mode 100644 index 0000000000000000000000000000000000000000..781b3b46c3e0adacd59fe554c0b576f6263dbe2a GIT binary patch literal 24569 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 literal 0 HcmV?d00001