Spaces:
Sleeping
Sleeping
Commit ·
88d676a
1
Parent(s): 3df8d41
app
Browse files
app.py
CHANGED
|
@@ -1,96 +1,160 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
from transformers import pipeline
|
|
|
|
|
|
|
| 3 |
import re
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
)
|
| 10 |
-
|
| 11 |
-
def process_application(company, monthly_revenue, monthly_expenses, requested_advance, additional_text):
|
| 12 |
-
# Calculate net income
|
| 13 |
-
net_income = monthly_revenue - monthly_expenses
|
| 14 |
-
|
| 15 |
-
# Define reasonable request rule for MCA
|
| 16 |
-
# Reasonable if advance <= 50% of net income and <= 80% of revenue
|
| 17 |
-
is_reasonable = (
|
| 18 |
-
(requested_advance <= (net_income * 0.5) if net_income > 0 else False)
|
| 19 |
-
and (requested_advance <= (monthly_revenue * 0.8))
|
| 20 |
-
)
|
| 21 |
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
if is_reasonable
|
| 25 |
-
else "\nNote: This request exceeds MCA reasonableness guidelines (advance > 50% of net income or > 80% of revenue)."
|
| 26 |
-
)
|
| 27 |
|
| 28 |
-
#
|
| 29 |
-
|
| 30 |
-
Company: {company}
|
| 31 |
-
Monthly Revenue: ${monthly_revenue:,}
|
| 32 |
-
Monthly Expenses: ${monthly_expenses:,}
|
| 33 |
-
Requested Advance: ${requested_advance:,}
|
| 34 |
-
{reasonable_text}
|
| 35 |
-
Additional Context: {additional_text or 'No additional context provided.'}
|
| 36 |
-
"""
|
| 37 |
|
| 38 |
-
#
|
| 39 |
-
|
| 40 |
-
label = result[0]['label']
|
| 41 |
-
score = result[0]['score']
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
score > 0.9 and
|
| 50 |
-
ratio > 1.2 and
|
| 51 |
-
advance_coverage > 1.0 and
|
| 52 |
-
is_reasonable):
|
| 53 |
-
decision = "Approve"
|
| 54 |
-
else:
|
| 55 |
-
decision = "Reject"
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
"
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
|
|
|
|
|
|
| 71 |
|
| 72 |
# Create Gradio interface
|
| 73 |
-
with gr.Blocks(title="
|
| 74 |
-
gr.Markdown("#
|
| 75 |
-
gr.Markdown("
|
| 76 |
|
| 77 |
with gr.Row():
|
| 78 |
with gr.Column():
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
submit_btn = gr.Button("Process Application", variant="primary")
|
| 86 |
|
| 87 |
with gr.Column():
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
submit_btn.click(
|
| 91 |
-
fn=
|
| 92 |
-
inputs=[
|
| 93 |
-
outputs=
|
| 94 |
)
|
| 95 |
|
| 96 |
if __name__ == "__main__":
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
from transformers import pipeline
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import pytesseract
|
| 5 |
import re
|
| 6 |
+
import json
|
| 7 |
|
| 8 |
+
def clean_ocr_text(raw_text):
|
| 9 |
+
"""Clean OCR text using regex - only for cleaning, not extraction"""
|
| 10 |
+
# Remove extra whitespace and normalize
|
| 11 |
+
cleaned = re.sub(r'\s+', ' ', raw_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
+
# Remove common OCR artifacts
|
| 14 |
+
cleaned = re.sub(r'[^\w\s\.,;:/\-@#$%&*()]', ' ', cleaned)
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
# Normalize common OCR mistakes
|
| 17 |
+
cleaned = re.sub(r'\b([A-Z])\s+([A-Z])\b', r'\1\2', cleaned) # Fix split letters
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
# Clean up date patterns
|
| 20 |
+
cleaned = re.sub(r'(\d{2})\s*/\s*(\d{2})\s*/\s*(\d{4})', r'\1/\2/\3', cleaned)
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
return cleaned.strip()
|
| 23 |
+
|
| 24 |
+
def extract_dl_info(image):
|
| 25 |
+
"""
|
| 26 |
+
Extracts driver's license information from an image file using OCR and AI model processing.
|
| 27 |
|
| 28 |
+
Args:
|
| 29 |
+
image: PIL Image object from Gradio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
+
Returns:
|
| 32 |
+
tuple: (raw_ocr_text, cleaned_ocr_text, json_result)
|
| 33 |
+
"""
|
| 34 |
+
try:
|
| 35 |
+
# Convert Gradio image to RGB
|
| 36 |
+
if hasattr(image, 'convert'):
|
| 37 |
+
image = image.convert("RGB")
|
| 38 |
+
|
| 39 |
+
# Use Tesseract OCR to extract text
|
| 40 |
+
print("Extracting text with Tesseract OCR...")
|
| 41 |
+
raw_text = pytesseract.image_to_string(image)
|
| 42 |
+
print(f"Raw OCR text: {raw_text}")
|
| 43 |
+
|
| 44 |
+
# Clean the OCR text using regex (only for cleaning, not extraction)
|
| 45 |
+
cleaned_text = clean_ocr_text(raw_text)
|
| 46 |
+
print(f"Cleaned OCR text: {cleaned_text}")
|
| 47 |
+
|
| 48 |
+
# Use AI model to process cleaned OCR text and extract structured information
|
| 49 |
+
print("Processing cleaned OCR text with AI model...")
|
| 50 |
+
|
| 51 |
+
# Load a text generation pipeline for structured extraction
|
| 52 |
+
generator = pipeline("text2text-generation", model="google/flan-t5-base")
|
| 53 |
+
|
| 54 |
+
# Create specific prompts for the AI model to extract individual fields
|
| 55 |
+
name_prompt = f"From this driver's license text, what is the person's full name? Text: {cleaned_text} Answer:"
|
| 56 |
+
address_prompt = f"From this driver's license text, what is the street address? Text: {cleaned_text} Answer:"
|
| 57 |
+
license_prompt = f"From this driver's license text, what is the license number (the long number)? Text: {cleaned_text} Answer:"
|
| 58 |
+
dob_prompt = f"From this driver's license text, what is the date of birth? Text: {cleaned_text} Answer:"
|
| 59 |
+
exp_prompt = f"From this driver's license text, what is the expiration date? Text: {cleaned_text} Answer:"
|
| 60 |
+
state_prompt = f"From this driver's license text, what is the state abbreviation? Text: {cleaned_text} Answer:"
|
| 61 |
+
|
| 62 |
+
# Extract each field separately using AI
|
| 63 |
+
try:
|
| 64 |
+
name_result = generator(name_prompt, max_length=50, do_sample=False)
|
| 65 |
+
name = name_result[0]['generated_text'].strip()
|
| 66 |
+
|
| 67 |
+
address_result = generator(address_prompt, max_length=50, do_sample=False)
|
| 68 |
+
address = address_result[0]['generated_text'].strip()
|
| 69 |
+
|
| 70 |
+
license_result = generator(license_prompt, max_length=50, do_sample=False)
|
| 71 |
+
license_number = license_result[0]['generated_text'].strip()
|
| 72 |
+
|
| 73 |
+
dob_result = generator(dob_prompt, max_length=50, do_sample=False)
|
| 74 |
+
date_of_birth = dob_result[0]['generated_text'].strip()
|
| 75 |
+
|
| 76 |
+
exp_result = generator(exp_prompt, max_length=50, do_sample=False)
|
| 77 |
+
expiration_date = exp_result[0]['generated_text'].strip()
|
| 78 |
+
|
| 79 |
+
state_result = generator(state_prompt, max_length=10, do_sample=False)
|
| 80 |
+
state = state_result[0]['generated_text'].strip()
|
| 81 |
+
|
| 82 |
+
# Clean up the AI responses
|
| 83 |
+
def clean_response(response, max_length=100):
|
| 84 |
+
if not response or response == "Not found":
|
| 85 |
+
return "Not found"
|
| 86 |
+
# Take only the first part if it's too long
|
| 87 |
+
if len(response) > max_length:
|
| 88 |
+
response = response[:max_length].split()[0] if response.split() else "Not found"
|
| 89 |
+
return response.strip()
|
| 90 |
+
|
| 91 |
+
# Create the extracted data
|
| 92 |
+
extracted = {
|
| 93 |
+
"name": clean_response(name, 50),
|
| 94 |
+
"address": clean_response(address, 100),
|
| 95 |
+
"license_number": clean_response(license_number, 30),
|
| 96 |
+
"date_of_birth": clean_response(date_of_birth, 20),
|
| 97 |
+
"expiration_date": clean_response(expiration_date, 20),
|
| 98 |
+
"state": clean_response(state, 10)
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
except Exception as e:
|
| 102 |
+
print(f"AI extraction failed: {e}")
|
| 103 |
+
extracted = {
|
| 104 |
+
"name": "AI extraction failed",
|
| 105 |
+
"address": "AI extraction failed",
|
| 106 |
+
"license_number": "AI extraction failed",
|
| 107 |
+
"date_of_birth": "AI extraction failed",
|
| 108 |
+
"expiration_date": "AI extraction failed",
|
| 109 |
+
"state": "AI extraction failed",
|
| 110 |
+
"error": f"AI model error: {str(e)}"
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
# Format JSON result
|
| 114 |
+
json_result = json.dumps(extracted, indent=2)
|
| 115 |
+
|
| 116 |
+
return raw_text, cleaned_text, json_result
|
| 117 |
|
| 118 |
+
except Exception as e:
|
| 119 |
+
error_msg = f"Processing failed: {str(e)}"
|
| 120 |
+
return error_msg, error_msg, json.dumps({"error": error_msg}, indent=2)
|
| 121 |
|
| 122 |
# Create Gradio interface
|
| 123 |
+
with gr.Blocks(title="Driver's License Information Extractor") as demo:
|
| 124 |
+
gr.Markdown("# 🆔 Driver's License Information Extractor")
|
| 125 |
+
gr.Markdown("Upload a driver's license image to extract information using OCR and AI processing. The system will show raw OCR text, cleaned text, and structured JSON results.")
|
| 126 |
|
| 127 |
with gr.Row():
|
| 128 |
with gr.Column():
|
| 129 |
+
image_input = gr.Image(
|
| 130 |
+
label="Upload Driver's License Image",
|
| 131 |
+
type="pil",
|
| 132 |
+
height=400
|
| 133 |
+
)
|
| 134 |
+
submit_btn = gr.Button("Extract Information", variant="primary")
|
|
|
|
| 135 |
|
| 136 |
with gr.Column():
|
| 137 |
+
raw_ocr = gr.Textbox(
|
| 138 |
+
label="Raw OCR Text",
|
| 139 |
+
lines=8,
|
| 140 |
+
interactive=False,
|
| 141 |
+
placeholder="Raw text extracted by Tesseract OCR will appear here..."
|
| 142 |
+
)
|
| 143 |
+
cleaned_text = gr.Textbox(
|
| 144 |
+
label="Cleaned OCR Text",
|
| 145 |
+
lines=8,
|
| 146 |
+
interactive=False,
|
| 147 |
+
placeholder="Cleaned and normalized text will appear here..."
|
| 148 |
+
)
|
| 149 |
+
json_result = gr.JSON(
|
| 150 |
+
label="Extracted Information (JSON)",
|
| 151 |
+
interactive=False
|
| 152 |
+
)
|
| 153 |
|
| 154 |
submit_btn.click(
|
| 155 |
+
fn=extract_dl_info,
|
| 156 |
+
inputs=[image_input],
|
| 157 |
+
outputs=[raw_ocr, cleaned_text, json_result]
|
| 158 |
)
|
| 159 |
|
| 160 |
if __name__ == "__main__":
|