File size: 6,577 Bytes
d9b720f
 
 
 
 
 
 
 
 
 
4aec7e0
d9b720f
 
 
 
4aec7e0
 
 
 
 
 
d9b720f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4aec7e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d9b720f
4aec7e0
 
 
 
d9b720f
4aec7e0
 
d9b720f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4aec7e0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import gradio as gr
import os
from dotenv import load_dotenv
import pandas as pd
from groq import Groq
from PIL import Image
import base64
import io
import openpyxl
from datetime import datetime
import httpx

# Load environment variables from .env file
load_dotenv()

def create_groq_client():
    api_key = os.environ.get("GROQ_API_KEY", "")
    return httpx.Client(
        base_url="https://api.groq.com/openai/v1",
        headers={"Authorization": f"Bearer {api_key}"}
    )

def encode_image_to_base64(image_path):
    """Convert image to base64 string"""
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')

def extract_invoice_details(image):
    """Extract invoice details using Groq's vision model"""
    # Save the uploaded image temporarily
    temp_path = "temp_invoice.png"
    image.save(temp_path)
    
    # Convert image to base64
    base64_image = encode_image_to_base64(temp_path)
    
    # Remove temporary file
    os.remove(temp_path)
    
    # Prepare the prompt
    prompt = """Analyze this invoice image and provide ONLY ONE dictionary with the following format, including all line items. Remove any special characters (*, #, $) and format numbers as plain decimal values:

    {
        "Invoice Number": "inv-00",  # Remove special chars, keep alphanumeric only
        "Invoice Date": "07/07/2025",  # Use MM/DD/YYYY format
        "Items": [
            {
                "Item Name": "Product 1",  # Clean text only
                "Price/Rate": "40.00",  # Numeric only
                "Quantity": "2",  # Numeric only
                "Amount": "80.00"  # Numeric only
            }
        ],
        "Total Invoice Value": "2555.00"  # Numeric only, total amount
    }

    Provide ONLY the dictionary, no additional text or formatting."""
    
    # Make API call to Groq
    client = create_groq_client()
    response = client.post(
        "/chat/completions",
        json={
            "model": "llama-3.2-90b-vision-preview",
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "image_url",
                            "image_url": {
                                "url": f"data:image/png;base64,{base64_image}"
                            }
                        },
                        {
                            "type": "text",
                            "text": prompt
                        }
                    ]
                }
            ]
        }
    )
    response_data = response.json()
    return response_data['choices'][0]['message']['content']

def parse_response(response_text):
    """Parse the model's response into structured data"""
    try:
        # Find the dictionary part of the response
        start_idx = response_text.find('{')
        end_idx = response_text.rfind('}') + 1
        if start_idx != -1 and end_idx != -1:
            dict_str = response_text[start_idx:end_idx]
            # Safely evaluate the dictionary string
            data = eval(dict_str)
            
            # Create rows for each item in the items list
            rows = []
            for item in data.get('Items', []):
                row = {
                    'Invoice Number': data.get('Invoice Number', ''),
                    'Invoice Date': data.get('Invoice Date', ''),
                    'Item Name': item.get('Item Name', ''),
                    'Price/Rate': item.get('Price/Rate', ''),
                    'Quantity': item.get('Quantity', ''),
                    'Amount': item.get('Amount', ''),
                    'Total Invoice Value': data.get('Total Invoice Value', '')
                }
                rows.append(row)
            
            return rows
    except Exception as e:
        print(f"Error parsing response: {e}")
        return [{
            'Invoice Number': '',
            'Invoice Date': '',
            'Item Name': '',
            'Price/Rate': '',
            'Quantity': '',
            'Amount': '',
            'Total Invoice Value': ''
        }]

def save_to_excel(data_rows):
    """Save cleaned extracted data to Excel file"""
    excel_file = "invoice_data.xlsx"
    
    # Create new DataFrame with the current data only
    df = pd.DataFrame(data_rows, columns=[
        'Invoice Number', 'Invoice Date', 'Item Name',
        'Price/Rate', 'Quantity', 'Amount', 'Total Invoice Value'
    ])
    
    # Apply number formatting for currency columns
    currency_columns = ['Price/Rate', 'Amount', 'Total Invoice Value']
    for col in currency_columns:
        df[col] = pd.to_numeric(df[col], errors='ignore')
    
    # Save to Excel with formatting
    with pd.ExcelWriter(excel_file, engine='openpyxl') as writer:
        df.to_excel(writer, index=False, sheet_name='Invoice Data')
        
        # Get the workbook and worksheet
        workbook = writer.book
        worksheet = writer.sheets['Invoice Data']
        
        # Apply currency formatting to relevant columns
        for col_idx, col_name in enumerate(df.columns):
            if col_name in currency_columns:
                for row in range(2, len(df) + 2):  # Start from row 2 to skip header
                    cell = worksheet.cell(row=row, column=col_idx + 1)
                    cell.number_format = '$#,##0.00'
    
    return excel_file

def process_invoice(image):
    """Main function to process invoice image"""
    try:
        # Extract text from image
        extracted_text = extract_invoice_details(image)
        
        # Parse the response
        data = parse_response(extracted_text)
        
        # Save to Excel
        excel_path = save_to_excel(data)
        
        return (
            f"Successfully processed invoice!\n\n"
            f"Extracted Information:\n{extracted_text}",
            excel_path
        )
    
    except Exception as e:
        return f"Error processing invoice: {str(e)}", None

# Create Gradio interface
iface = gr.Interface(
    fn=process_invoice,
    inputs=gr.Image(type="pil", label="Upload Handwritten Invoice"),
    outputs=[
        gr.Textbox(label="Processing Result"),
        gr.File(label="Download Excel File")
    ],
    title="Handwritten Invoice Processor",
    description="Upload a handwritten invoice to extract key information and save it to Excel.",
    examples=[],
    theme=gr.themes.Base()
)

# Launch the application
if __name__ == "__main__":
    iface.launch(server_name="0.0.0.0", server_port=7860)