ngupta2026 commited on
Commit
f5ff3d2
·
verified ·
1 Parent(s): 2f0bde3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -162
app.py CHANGED
@@ -6,78 +6,29 @@ import re
6
  import requests
7
  import os
8
 
9
- RESEND_API_KEY = os.getenv("RESEND_API_KEY")
10
-
11
- def send_claim_email(to_email, extracted):
12
-
13
- if not RESEND_API_KEY:
14
- return "❌ API key missing"
15
-
16
- subject = "Insurance Claim Request"
17
-
18
- html_body = f"""
19
- <h2>Insurance Claim</h2>
20
- <p><b>Provider:</b> {extracted['company']}</p>
21
- <p><b>Date:</b> {extracted['date']}</p>
22
- <p><b>Amount:</b> ₹{extracted['total']}</p>
23
- """
24
-
25
- try:
26
- response = requests.post(
27
- "https://api.resend.com/emails",
28
- headers={
29
- "Authorization": f"Bearer {RESEND_API_KEY}",
30
- "Content-Type": "application/json",
31
- },
32
- json={
33
- "from": "onboarding@resend.dev",
34
- "to": [to_email],
35
- "subject": subject,
36
- "html": html_body,
37
- },
38
- timeout=10
39
- )
40
-
41
- if response.status_code == 200:
42
- return f"✅ Email sent to {to_email}"
43
- else:
44
- return f"❌ Failed: {response.text}"
45
-
46
- except Exception as e:
47
- return f"❌ Error: {str(e)}"
48
-
49
- from email.mime.text import MIMEText
50
- from email.mime.multipart import MIMEMultipart
51
-
52
  from transformers import LayoutLMTokenizerFast, LayoutLMForTokenClassification
53
 
54
  # =====================================================
55
- # LABELS
56
  # =====================================================
 
 
57
  label2id = {"O": 0, "COMPANY": 1, "DATE": 2, "TOTAL": 3}
58
  id2label = {v: k for k, v in label2id.items()}
59
 
 
 
60
  # =====================================================
61
  # LOAD MODEL
62
  # =====================================================
63
- MODEL_NAME = "ngupta2026/sroie-layoutlm"
64
 
65
  model = LayoutLMForTokenClassification.from_pretrained(MODEL_NAME)
66
  tokenizer = LayoutLMTokenizerFast.from_pretrained(MODEL_NAME)
67
 
68
- device = "cuda" if torch.cuda.is_available() else "cpu"
69
  model.to(device)
70
  model.eval()
71
 
72
- # =====================================================
73
- # EMAIL CONFIG
74
- # Add these in Hugging Face Space Secrets:
75
- # EMAIL_USER = yourgmail@gmail.com
76
- # EMAIL_PASS = your_app_password
77
- # =====================================================
78
- EMAIL_USER = os.getenv("EMAIL_USER")
79
- EMAIL_PASS = os.getenv("EMAIL_PASS")
80
-
81
  # =====================================================
82
  # NORMALIZE BOXES
83
  # =====================================================
@@ -93,157 +44,149 @@ def normalize(box, width, height):
93
  # EXTRACT DATA
94
  # =====================================================
95
  def extract_receipt(image):
96
- image = image.convert("RGB")
97
- image.thumbnail((1200, 1200))
98
-
99
- data = pytesseract.image_to_data(
100
- image,
101
- output_type=pytesseract.Output.DICT
102
- )
103
-
104
- words = []
105
- boxes = []
106
-
107
- for i in range(len(data["text"])):
108
 
109
- text = data["text"][i].strip()
 
 
 
110
 
111
- if text.strip() != "" and len(text) > 2:
112
- x = data["left"][i]
113
- y = data["top"][i]
114
- w = data["width"][i]
115
- h = data["height"][i]
116
 
117
- words.append(text)
118
- boxes.append([x, y, x + w, y + h])
119
 
120
- if len(words) == 0:
121
- return {"error": "No text detected"}
 
 
 
122
 
123
- width, height = image.size
124
- boxes = [normalize(box, width, height) for box in boxes]
125
 
126
- encoding = tokenizer(
127
- words,
128
- boxes=boxes,
129
- return_tensors="pt",
130
- padding="max_length",
131
- truncation=True,
132
- is_split_into_words=True,
133
- max_length=256
134
- )
135
 
136
- encoding = {k: v.to(device) for k, v in encoding.items()}
 
137
 
138
- with torch.no_grad():
139
- outputs = model(**encoding)
 
 
 
 
 
 
 
140
 
141
- predictions = torch.argmax(outputs.logits, dim=2)[0][:len(words)]
142
 
143
- result = {
144
- "company": [],
145
- "date": [],
146
- "total": []
147
- }
148
 
149
- for word, pred in zip(words, predictions):
150
 
151
- label = id2label[pred.item()]
 
 
 
 
152
 
153
- # company from model
154
- if label == "COMPANY":
155
- result["company"].append(word)
156
 
157
- # date from regex
158
- if re.search(r"\d{2}[/-]\d{2}[/-]\d{2,4}", word):
159
- result["date"].append(word)
160
 
161
- # total from regex
162
- if re.search(r"\d+(\.\d{2})?", word):
163
- try:
164
- value = float(word.replace(",", ""))
165
- if value > 50:
166
- result["total"].append(word)
167
- except:
168
- pass
169
 
170
- result["company"] = (
171
- " ".join(result["company"])
172
- if result["company"] else "Not Found"
173
- )
 
 
 
174
 
175
- result["date"] = (
176
- result["date"][0]
177
- if result["date"] else "Not Found"
178
- )
179
 
180
- result["total"] = (
181
- result["total"][-1]
182
- if result["total"] else "Not Found"
183
- )
184
 
185
- return result
 
186
 
187
  # =====================================================
188
- # SEND EMAIL
189
  # =====================================================
190
  def send_claim_email(to_email, extracted):
191
 
192
- if not EMAIL_USER or not EMAIL_PASS:
193
- return "Email secrets not configured."
194
 
195
  subject = "Insurance Claim Request"
196
 
197
- body = f"""
198
- Dear Claims Team,
199
-
200
- I would like to request reimbursement for an eligible expense.
201
-
202
- Provider Name: {extracted['company']}
203
- Bill Date: {extracted['date']}
204
- Claim Amount: ₹{extracted['total']}
205
-
206
- Please process the claim.
207
-
208
- Regards
209
- Customer
210
- """
211
-
212
- msg = MIMEMultipart()
213
- msg["From"] = EMAIL_USER
214
- msg["To"] = to_email
215
- msg["Subject"] = subject
216
-
217
- msg.attach(MIMEText(body, "plain"))
218
 
219
  try:
220
- server = smtplib.SMTP("smtp.gmail.com", 587)
221
- server.starttls()
222
- server.login(EMAIL_USER, EMAIL_PASS)
223
- server.sendmail(
224
- EMAIL_USER,
225
- to_email,
226
- msg.as_string()
 
 
 
 
 
 
227
  )
228
- server.quit()
229
 
230
- return f" Email sent successfully to {to_email}"
 
 
 
 
 
231
 
232
  except Exception as e:
233
- return f"❌ Email failed: {str(e)}"
234
 
235
  # =====================================================
236
- # MAIN UI FUNCTION
237
  # =====================================================
238
  def process_and_send(image, email_id):
239
 
 
 
240
  extracted = extract_receipt(image)
 
241
 
242
  if "error" in extracted:
243
  return extracted, extracted["error"]
244
 
 
 
245
  email_status = send_claim_email(email_id, extracted)
246
 
 
 
247
  return extracted, email_status
248
 
249
  # =====================================================
@@ -253,7 +196,7 @@ demo = gr.Interface(
253
  fn=process_and_send,
254
  inputs=[
255
  gr.Image(type="pil", label="Upload Receipt"),
256
- gr.Textbox(label="Insurance Email ID")
257
  ],
258
  outputs=[
259
  gr.JSON(label="Extracted Data"),
 
6
  import requests
7
  import os
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  from transformers import LayoutLMTokenizerFast, LayoutLMForTokenClassification
10
 
11
  # =====================================================
12
+ # CONFIG
13
  # =====================================================
14
+ RESEND_API_KEY = os.getenv("RESEND_API_KEY")
15
+
16
  label2id = {"O": 0, "COMPANY": 1, "DATE": 2, "TOTAL": 3}
17
  id2label = {v: k for k, v in label2id.items()}
18
 
19
+ MODEL_NAME = "ngupta2026/sroie-layoutlm"
20
+
21
  # =====================================================
22
  # LOAD MODEL
23
  # =====================================================
24
+ device = "cuda" if torch.cuda.is_available() else "cpu"
25
 
26
  model = LayoutLMForTokenClassification.from_pretrained(MODEL_NAME)
27
  tokenizer = LayoutLMTokenizerFast.from_pretrained(MODEL_NAME)
28
 
 
29
  model.to(device)
30
  model.eval()
31
 
 
 
 
 
 
 
 
 
 
32
  # =====================================================
33
  # NORMALIZE BOXES
34
  # =====================================================
 
44
  # EXTRACT DATA
45
  # =====================================================
46
  def extract_receipt(image):
47
+ try:
48
+ # 🔥 Speed optimization
49
+ image = image.convert("RGB")
50
+ image.thumbnail((1200, 1200))
 
 
 
 
 
 
 
 
51
 
52
+ data = pytesseract.image_to_data(
53
+ image,
54
+ output_type=pytesseract.Output.DICT
55
+ )
56
 
57
+ words = []
58
+ boxes = []
 
 
 
59
 
60
+ for i in range(len(data["text"])):
61
+ text = data["text"][i].strip()
62
 
63
+ if text != "" and len(text) > 2:
64
+ x = data["left"][i]
65
+ y = data["top"][i]
66
+ w = data["width"][i]
67
+ h = data["height"][i]
68
 
69
+ words.append(text)
70
+ boxes.append([x, y, x + w, y + h])
71
 
72
+ if len(words) == 0:
73
+ return {"error": "No text detected"}
 
 
 
 
 
 
 
74
 
75
+ width, height = image.size
76
+ boxes = [normalize(box, width, height) for box in boxes]
77
 
78
+ encoding = tokenizer(
79
+ words,
80
+ boxes=boxes,
81
+ return_tensors="pt",
82
+ padding="max_length",
83
+ truncation=True,
84
+ is_split_into_words=True,
85
+ max_length=256
86
+ )
87
 
88
+ encoding = {k: v.to(device) for k, v in encoding.items()}
89
 
90
+ with torch.no_grad():
91
+ outputs = model(**encoding)
 
 
 
92
 
93
+ predictions = torch.argmax(outputs.logits, dim=2)[0][:len(words)]
94
 
95
+ result = {
96
+ "company": [],
97
+ "date": [],
98
+ "total": []
99
+ }
100
 
101
+ for word, pred in zip(words, predictions):
102
+ label = id2label[pred.item()]
 
103
 
104
+ if label == "COMPANY":
105
+ result["company"].append(word)
 
106
 
107
+ if re.search(r"\d{2}[/-]\d{2}[/-]\d{2,4}", word):
108
+ result["date"].append(word)
 
 
 
 
 
 
109
 
110
+ if re.search(r"\d+(\.\d{2})?", word):
111
+ try:
112
+ value = float(word.replace(",", ""))
113
+ if value > 50:
114
+ result["total"].append(word)
115
+ except:
116
+ pass
117
 
118
+ result["company"] = " ".join(result["company"]) if result["company"] else "Not Found"
119
+ result["date"] = result["date"][0] if result["date"] else "Not Found"
120
+ result["total"] = result["total"][-1] if result["total"] else "Not Found"
 
121
 
122
+ return result
 
 
 
123
 
124
+ except Exception as e:
125
+ return {"error": str(e)}
126
 
127
  # =====================================================
128
+ # SEND EMAIL (RESEND API - WORKING)
129
  # =====================================================
130
  def send_claim_email(to_email, extracted):
131
 
132
+ if not RESEND_API_KEY:
133
+ return " RESEND_API_KEY missing in HuggingFace Secrets"
134
 
135
  subject = "Insurance Claim Request"
136
 
137
+ html_body = f"""
138
+ <h2>Insurance Claim Request</h2>
139
+ <p><b>Provider:</b> {extracted['company']}</p>
140
+ <p><b>Date:</b> {extracted['date']}</p>
141
+ <p><b>Amount:</b> ₹{extracted['total']}</p>
142
+ <p>Please process this claim.</p>
143
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
  try:
146
+ response = requests.post(
147
+ "https://api.resend.com/emails",
148
+ headers={
149
+ "Authorization": f"Bearer {RESEND_API_KEY}",
150
+ "Content-Type": "application/json",
151
+ },
152
+ json={
153
+ "from": "onboarding@resend.dev",
154
+ "to": [to_email],
155
+ "subject": subject,
156
+ "html": html_body,
157
+ },
158
+ timeout=10
159
  )
 
160
 
161
+ print("EMAIL RESPONSE:", response.status_code, response.text)
162
+
163
+ if response.status_code in [200, 201]:
164
+ return f"✅ Email sent successfully to {to_email}"
165
+ else:
166
+ return f"❌ Email failed: {response.text}"
167
 
168
  except Exception as e:
169
+ return f"❌ Email error: {str(e)}"
170
 
171
  # =====================================================
172
+ # MAIN FUNCTION
173
  # =====================================================
174
  def process_and_send(image, email_id):
175
 
176
+ print("Processing started...")
177
+
178
  extracted = extract_receipt(image)
179
+ print("Extracted:", extracted)
180
 
181
  if "error" in extracted:
182
  return extracted, extracted["error"]
183
 
184
+ print("Sending email to:", email_id)
185
+
186
  email_status = send_claim_email(email_id, extracted)
187
 
188
+ print("Email status:", email_status)
189
+
190
  return extracted, email_status
191
 
192
  # =====================================================
 
196
  fn=process_and_send,
197
  inputs=[
198
  gr.Image(type="pil", label="Upload Receipt"),
199
+ gr.Textbox(label="Enter Email ID")
200
  ],
201
  outputs=[
202
  gr.JSON(label="Extracted Data"),