Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import cv2
|
|
| 5 |
import boto3
|
| 6 |
import os
|
| 7 |
import json
|
|
|
|
| 8 |
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 9 |
from rapidocr_onnxruntime import RapidOCR
|
| 10 |
from openai import OpenAI
|
|
@@ -24,6 +25,9 @@ if not OPENAI_API_KEY:
|
|
| 24 |
|
| 25 |
client = OpenAI(api_key=OPENAI_API_KEY)
|
| 26 |
|
|
|
|
|
|
|
|
|
|
| 27 |
# S3 client
|
| 28 |
s3 = boto3.client(
|
| 29 |
"s3",
|
|
@@ -82,14 +86,12 @@ async def generate(image_id: str):
|
|
| 82 |
if not result:
|
| 83 |
raise HTTPException(status_code=500, detail="OCR returned empty result")
|
| 84 |
|
| 85 |
-
# Full extracted text
|
| 86 |
full_text = "\n".join([text for _, text, _ in result])
|
| 87 |
|
| 88 |
# -------- CONFIDENCE SCORE --------
|
| 89 |
confidences = [conf for _, _, conf in result if isinstance(conf, (int, float))]
|
| 90 |
avg_confidence = sum(confidences) / len(confidences) if confidences else 0
|
| 91 |
|
| 92 |
-
# If confidence is low → return early
|
| 93 |
if avg_confidence < 0.70:
|
| 94 |
return {
|
| 95 |
"image_id": image_id,
|
|
@@ -98,7 +100,7 @@ async def generate(image_id: str):
|
|
| 98 |
"message": "Upload image with more clarity or enter manually."
|
| 99 |
}
|
| 100 |
|
| 101 |
-
# --------
|
| 102 |
schema = {
|
| 103 |
"name": "extract_expense_details",
|
| 104 |
"schema": {
|
|
@@ -118,7 +120,7 @@ async def generate(image_id: str):
|
|
| 118 |
}
|
| 119 |
}
|
| 120 |
|
| 121 |
-
# --------
|
| 122 |
prompt = f"""
|
| 123 |
You are an expense extraction AI.
|
| 124 |
|
|
@@ -144,29 +146,21 @@ Extract expense details from the OCR text below:
|
|
| 144 |
Always generate notes EXACTLY in this format:
|
| 145 |
"Spent <total_amount> on <label> on <date>."
|
| 146 |
|
| 147 |
-
- If <total_amount> is unknown → use "unknown"
|
| 148 |
-
- If <label> is unknown → use "unknown"
|
| 149 |
-
- If <date> is unknown → use "unknown"
|
| 150 |
-
|
| 151 |
### Required Output:
|
| 152 |
Return structured JSON (via schema) with:
|
| 153 |
-
- total_amount
|
| 154 |
-
- label
|
| 155 |
-
- date
|
| 156 |
-
- time
|
| 157 |
-
- payment_type
|
| 158 |
-
- notes
|
| 159 |
-
|
| 160 |
-
Fill **every** field ONLY with information found in the extracted text or "unknown".
|
| 161 |
"""
|
| 162 |
|
|
|
|
| 163 |
try:
|
| 164 |
response = client.chat.completions.create(
|
| 165 |
model="gpt-4o-mini",
|
| 166 |
-
response_format={
|
| 167 |
-
"type": "json_schema",
|
| 168 |
-
"json_schema": schema
|
| 169 |
-
},
|
| 170 |
messages=[
|
| 171 |
{"role": "system", "content": "You are an expert in receipt parsing."},
|
| 172 |
{"role": "user", "content": prompt}
|
|
@@ -179,6 +173,26 @@ Fill **every** field ONLY with information found in the extracted text or "unkno
|
|
| 179 |
except Exception as e:
|
| 180 |
raise HTTPException(status_code=500, detail=f"OpenAI Error: {str(e)}")
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
return {
|
| 183 |
"image_id": image_id,
|
| 184 |
"raw_text": full_text,
|
|
|
|
| 5 |
import boto3
|
| 6 |
import os
|
| 7 |
import json
|
| 8 |
+
import requests
|
| 9 |
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 10 |
from rapidocr_onnxruntime import RapidOCR
|
| 11 |
from openai import OpenAI
|
|
|
|
| 25 |
|
| 26 |
client = OpenAI(api_key=OPENAI_API_KEY)
|
| 27 |
|
| 28 |
+
# Category API URL
|
| 29 |
+
CATEGORY_API_URL = "https://logicgoinfotechspaces-auto-expense-categorization.hf.space/api/labels"
|
| 30 |
+
|
| 31 |
# S3 client
|
| 32 |
s3 = boto3.client(
|
| 33 |
"s3",
|
|
|
|
| 86 |
if not result:
|
| 87 |
raise HTTPException(status_code=500, detail="OCR returned empty result")
|
| 88 |
|
|
|
|
| 89 |
full_text = "\n".join([text for _, text, _ in result])
|
| 90 |
|
| 91 |
# -------- CONFIDENCE SCORE --------
|
| 92 |
confidences = [conf for _, _, conf in result if isinstance(conf, (int, float))]
|
| 93 |
avg_confidence = sum(confidences) / len(confidences) if confidences else 0
|
| 94 |
|
|
|
|
| 95 |
if avg_confidence < 0.70:
|
| 96 |
return {
|
| 97 |
"image_id": image_id,
|
|
|
|
| 100 |
"message": "Upload image with more clarity or enter manually."
|
| 101 |
}
|
| 102 |
|
| 103 |
+
# -------- JSON SCHEMA FOR GPT --------
|
| 104 |
schema = {
|
| 105 |
"name": "extract_expense_details",
|
| 106 |
"schema": {
|
|
|
|
| 120 |
}
|
| 121 |
}
|
| 122 |
|
| 123 |
+
# -------- PROMPT --------
|
| 124 |
prompt = f"""
|
| 125 |
You are an expense extraction AI.
|
| 126 |
|
|
|
|
| 146 |
Always generate notes EXACTLY in this format:
|
| 147 |
"Spent <total_amount> on <label> on <date>."
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
### Required Output:
|
| 150 |
Return structured JSON (via schema) with:
|
| 151 |
+
- total_amount
|
| 152 |
+
- label
|
| 153 |
+
- date
|
| 154 |
+
- time
|
| 155 |
+
- payment_type
|
| 156 |
+
- notes
|
|
|
|
|
|
|
| 157 |
"""
|
| 158 |
|
| 159 |
+
# -------- CALL GPT --------
|
| 160 |
try:
|
| 161 |
response = client.chat.completions.create(
|
| 162 |
model="gpt-4o-mini",
|
| 163 |
+
response_format={"type": "json_schema", "json_schema": schema},
|
|
|
|
|
|
|
|
|
|
| 164 |
messages=[
|
| 165 |
{"role": "system", "content": "You are an expert in receipt parsing."},
|
| 166 |
{"role": "user", "content": prompt}
|
|
|
|
| 173 |
except Exception as e:
|
| 174 |
raise HTTPException(status_code=500, detail=f"OpenAI Error: {str(e)}")
|
| 175 |
|
| 176 |
+
# -------- CATEGORY API CALL --------
|
| 177 |
+
extracted_label = parsed.get("label", "unknown")
|
| 178 |
+
|
| 179 |
+
try:
|
| 180 |
+
cat_response = requests.post(
|
| 181 |
+
CATEGORY_API_URL,
|
| 182 |
+
json={"label": extracted_label},
|
| 183 |
+
timeout=10
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
if cat_response.status_code == 200:
|
| 187 |
+
cat_data = cat_response.json()
|
| 188 |
+
parsed["category"] = cat_data.get("category", "unknown")
|
| 189 |
+
else:
|
| 190 |
+
parsed["category"] = "unknown"
|
| 191 |
+
|
| 192 |
+
except Exception:
|
| 193 |
+
parsed["category"] = "unknown"
|
| 194 |
+
|
| 195 |
+
# -------- FINAL RESPONSE --------
|
| 196 |
return {
|
| 197 |
"image_id": image_id,
|
| 198 |
"raw_text": full_text,
|