joschetan's picture
Update app.py
6305eba verified
Raw
History Blame Contribute Delete
21 kB
import gradio as gr
import pandas as pd
import requests
import json
import pdfplumber
import re
import spaces
from processor import process_invoices_backend
from google_sheet_sync import (
fetch_all_from_sheet, push_rules_to_sheet, push_template_file_to_sheet,
load_template_bytes_from_sheet
)
from pdf_engine import extract_header_value
WEB_APP_URL = "https://script.google.com/macros/s/AKfycbxxKu82K0V3VFn7tRlj3ddzB7Wy-iPyrupAV9tZzjBdhcXZPWFIXodJv6D03M-X_KSn/exec"
SPREADSHEET_ID = "1GJlIl_ANRPs4lID1aZ4WZiWVg_m5AQd7cuQSx6LCnxw"
def fetch_shipper_names():
try:
data = fetch_all_from_sheet()
if isinstance(data, dict) and "shippers" in data:
names = list(data["shippers"].keys())
if names:
return gr.update(choices=names, value=None)
json_db_url = f"https://docs.google.com/spreadsheets/d/{SPREADSHEET_ID}/gviz/tq?tqx=out:csv&sheet=Shipper_JSON_Database"
df_json = pd.read_csv(json_db_url)
if not df_json.empty and "ShipperName" in df_json.columns:
names = df_json["ShipperName"].dropna().tolist()
if names:
return gr.update(choices=names, value=None)
except Exception:
pass
return gr.update(choices=[], value=None)
@spaces.GPU
def process_invoice_action(shipper, main_inv, gst_inv, deec_decl):
if not shipper:
return None, "⚠️ Kripya pehle shipper chunein!"
sheet_data = fetch_all_from_sheet()
shippers_dict = sheet_data.get("shippers", {}) if sheet_data else {}
shipper_info = shippers_dict.get(shipper, {})
file_bytes, filename_or_err = process_invoices_backend(shipper, shipper_info, main_inv, gst_inv, deec_decl)
if file_bytes is None:
return None, f"❌ Error: {filename_or_err}"
import tempfile
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
tmp.write(file_bytes)
tmp.close()
return tmp.name, f"🎉 Success! File '{filename_or_err}' taiyar hai."
@spaces.GPU
def process_rate_pdf(file_obj):
if file_obj is None:
return "Kripya PDF file upload karein!"
try:
with pdfplumber.open(file_obj.name) as pdf:
text = ""
for page in pdf.pages:
t = page.extract_text()
if t: text += t + "\n"
date_match = re.search(r"w\.e\.f[\s\.:]*([\d]{2}[\-\/][\d]{2}[\-\/][\d]{4})", text, re.IGNORECASE)
rate_date = date_match.group(1).strip() if date_match else "N/A"
return f"🎉 Rate PDF Successfully Parsed! w.e.f: {rate_date}"
except Exception as e:
return f"Error: {str(e)}"
def verify_password(pwd):
if pwd == "CKJ":
return gr.update(visible=False), gr.update(visible=True)
else:
return gr.update(visible=True), gr.update(visible=False)
def verify_admin(pwd):
if pwd == "TEST":
return gr.update(visible=False), gr.update(visible=True)
else:
return gr.update(visible=True), gr.update(visible=False)
def on_shipper_select(shipper):
if shipper and shipper.strip():
return gr.update(visible=True)
return gr.update(visible=False)
def on_file_upload(file_obj):
if file_obj is not None:
return gr.update(visible=True)
return gr.update(visible=False)
custom_css = """
.creator-card {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
padding: 10px;
border-radius: 8px;
color: white;
text-align: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.15);
margin-bottom: 8px;
margin-top: 6px;
max-width: 220px;
margin-left: auto;
margin-right: auto;
}
.creator-name {
font-size: 15px;
font-weight: 700;
margin-top: 4px;
margin-bottom: 2px;
}
.creator-title {
font-size: 10px;
color: #d1d8e0;
letter-spacing: 1px;
text-transform: uppercase;
font-weight: 600;
}
"""
with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
# 🔒 1. Global Password Lock Screen
with gr.Column(visible=True) as login_screen:
with gr.Row():
gr.Column(scale=1)
with gr.Column(scale=2):
gr.Markdown("<br><br><h2 style='text-align: center;'>🚢 CK Export Invoice Processor Pro</h2>")
gr.Markdown("<p style='text-align: center; color: gray;'>Kripya aage badhne ke liye app ka password darj karein.</p>")
pass_input = gr.Textbox(label="Password darj karein:", type="password")
unlock_btn = gr.Button("Unlock App", variant="primary")
gr.Column(scale=1)
# 🚀 2. Main Application Layout
with gr.Column(visible=False) as main_app:
with gr.Row():
# 📌 Left Sidebar
with gr.Column(scale=1, min_width=240):
try:
gr.Image("ck_photo.jpg", show_label=False, interactive=False, width=210)
except:
gr.Markdown("*(Chetan Joshi Photo)*")
gr.HTML("""
<div class="creator-card">
<div class="creator-name">Chetan Joshi</div>
<div class="creator-title">📞 +91 98253 06898</div>
<hr style="border-color: rgba(255,255,255,0.2); margin: 4px 0;">
<p style='font-size: 9px; color: #f1d8e6; margin: 0;'>
<b>CK Export Invoice Pro v2.0</b><br>
Enterprise Automation & Precision.
</p>
</div>
""")
with gr.Row():
gr.Number(value=109.8, label="EUR", interactive=False)
gr.Number(value=128.15, label="GBP", interactive=False)
gr.Number(value=94.8, label="USD", interactive=False)
gr.HTML("<div style='text-align: center; margin-top: 6px; margin-bottom: 8px;'><span style='font-size: 15px; font-weight: 800; color: #00cec9; background: rgba(0, 206, 201, 0.1); padding: 4px 8px; border-radius: 6px; display: inline-block;'>📅 w.e.f: N/A</span></div>")
gr.Markdown("---")
gr.Markdown("##### 💱 Customs Exchange Rates")
pdf_rate_input = gr.File(label="Upload Rate PDF", file_types=[".pdf"])
rate_output = gr.Textbox(label="Status", interactive=False)
pdf_rate_input.change(fn=process_rate_pdf, inputs=pdf_rate_input, outputs=rate_output)
# 📌 Right Main Dashboard
with gr.Column(scale=3):
with gr.Column(visible=True) as user_dashboard:
gr.Markdown("# 🚢 CK Export Invoice Processor")
gr.Markdown("---")
gr.Markdown("### 📥 Invoice Processing Zone (Multi-Document)")
shipper_dropdown = gr.Dropdown(
choices=[],
label="किस शिपर का इनवॉइस प्रोसेस करना है?",
interactive=True,
value=None
)
with gr.Column(visible=False) as upload_section:
gr.Markdown("### 📄 Upload Invoices & Supporting Documents")
with gr.Row():
main_inv_file = gr.File(label="मुख्य इनवॉइस (PDF / Excel) #1")
gst_inv_file = gr.File(label="GST Invoice #1 (PDF/Excel)")
deec_decl_file = gr.File(label="DEEC Decl. #1 (PDF/Excel)")
with gr.Column(visible=False) as process_section:
process_btn = gr.Button("🚀 Process & Generate Excel (ZeroGPU)", variant="primary")
output_file = gr.File(label="📥 Download Generated Excel")
output_status = gr.Textbox(label="Processing Status")
main_inv_file.change(fn=on_file_upload, inputs=main_inv_file, outputs=process_section)
process_btn.click(
fn=process_invoice_action,
inputs=[shipper_dropdown, main_inv_file, gst_inv_file, deec_decl_file],
outputs=[output_file, output_status]
)
shipper_dropdown.change(fn=on_shipper_select, inputs=shipper_dropdown, outputs=upload_section)
gr.Markdown("---")
with gr.Accordion("🛠️ Admin Settings Access", open=False):
admin_pwd_input = gr.Textbox(label="Admin password darj karein:", type="password")
admin_login_btn = gr.Button("Login Karein")
# Admin Mode View with Live Google Sheet Binding
with gr.Column(visible=False) as admin_dashboard:
gr.Markdown("# 🛠️ CK Export Processor - Admin Mode")
admin_back_btn = gr.Button("🚪 Log Out Admin", variant="primary")
admin_tabs = gr.Radio(
["i. 🏢 Add Shipper Name & Setup", "iii. 🌍 Global Masters & Common Dictionaries"],
label="📋 Admin settings chunein:",
value="i. 🏢 Add Shipper Name & Setup"
)
with gr.Column() as admin_content_box:
gr.Markdown("### 🏢 Add Shipper Name & No-Code Visual Mapping Builder")
admin_shipper_dropdown = gr.Dropdown(choices=[], label="1. कॉन्फ़िगर करने के लिए शिपर चुनें:", interactive=True)
gr.Markdown("---")
gr.Markdown("### 📁 2. टेम्पलेट फ़ाइल अपलोड (Full Job Excel Template)")
with gr.Row():
tpl_file_input = gr.File(label="Blank Full Job Excel Format File (Template) चुनें", file_types=[".xlsx", ".xls"])
tpl_save_btn = gr.Button("🚀 Save Template to Google Sheet", variant="primary")
tpl_status_box = gr.Textbox(label="Template Status", interactive=False)
gr.Markdown("---")
gr.Markdown("### 🧪 3. Sample PDF Upload & Text Viewer")
sample_pdf_input = gr.File(label="टेस्ट करने के लिए सैंपल इनवॉइस PDF अपलोड करें", file_types=[".pdf"])
sample_text_output = gr.Textbox(label="Extracted PDF Text Preview", lines=6, interactive=False)
def load_sample_pdf_text(file_obj):
if file_obj is None:
return "कोई फाइल अपलोड नहीं की गई।"
try:
with pdfplumber.open(file_obj.name) as pdf:
txt = "".join([p.extract_text() or "" for p in pdf.pages])
return txt[:3000] + "\n...(Text truncated)..." if len(txt) > 3000 else txt
except Exception as e:
return f"Error reading PDF: {str(e)}"
sample_pdf_input.change(fn=load_sample_pdf_text, inputs=sample_pdf_input, outputs=sample_text_output)
gr.Markdown("---")
gr.Markdown("### ⚡ 4. Smart Test & Save Generator (Box & Position)")
with gr.Row():
test_target_input = gr.Textbox(label="1. टारगेट वैल्यू / फील्ड नाम:")
test_kw_input = gr.Textbox(label="2. मुख्य कीवर्ड:")
test_pos_dropdown = gr.Dropdown(["Right (आगे)", "📦 Extract Inside Box (डब्बे के अंदर का टेक्स्ट)", "Below (नीचे)"], label="3. दिशा / तरीका:", value="Right (आगे)")
test_index_number = gr.Number(value=1, label="4. Index:")
test_run_btn = gr.Button("🚀 Run Live Single Field Inspection", variant="primary")
test_result_output = gr.Textbox(label="Inspection Result", interactive=False)
def run_live_inspection(shipper_name, sample_file, target_field, keyword, direction, index_val):
if not sample_file:
return "⚠️ कृपया पहले सैंपल PDF अपलोड करें!"
if not keyword:
return "⚠️ कृपया मुख्य कीवर्ड दर्ज करें!"
try:
with pdfplumber.open(sample_file.name) as pdf:
pdf_text = "".join([p.extract_text() or "" for p in pdf.pages])
pdf_lines = pdf_text.split("\n")
# Call core extraction engine
res = extract_header_value(pdf_lines, pdf_text, keyword, direction, "Exact Word", "", "None", field_label=target_field)
return f"🎯 Result Found: {res}" if res else "❌ No value found for this keyword/direction."
except Exception as e:
return f"Error during inspection: {str(e)}"
test_run_btn.click(
fn=run_live_inspection,
inputs=[admin_shipper_dropdown, sample_pdf_input, test_target_input, test_kw_input, test_pos_dropdown, test_index_number],
outputs=test_result_output
)
gr.Markdown("---")
gr.Markdown("### 🛠️ 5. Header Fields Mapping & Regex Rules")
header_rules_table = gr.Dataframe(
headers=["Field Name", "Source Doc", "Keyword", "Cell", "Prompt", "Result Ex"],
datatype=["str", "str", "str", "str", "str", "str"],
row_count=5,
col_count=6,
label="Header Rules Configuration",
interactive=True
)
gr.Markdown("---")
gr.Markdown("### 📋 6. Dynamic Item Table Rules & Mapping")
parser_selector = gr.Dropdown(["parser_welspun", "parser_polycab", "parser_bkt", "parser_vapi_welspun"], label="इस शिपर के लिए आइटम पार्सर चुनें:", value="parser_welspun")
item_rules_table = gr.Dataframe(
headers=["Item Field Name", "Excel Col", "Source Type", "Extraction Rule / Keyword", "Result Example"],
datatype=["str", "str", "str", "str", "str"],
row_count=5,
col_count=5,
label="Item Table Rules Configuration",
interactive=True
)
gr.Markdown("---")
gr.Markdown("### ⚙️ 7. IGST & Lut Configuration")
with gr.Row():
lut_keywords_input = gr.Textbox(label="LUT Keywords (कॉमा से अलग करें):", value="LUT, UNDER LUT, UNDER BOND")
paid_keywords_input = gr.Textbox(label="Paid Keywords (कॉमा से अलग करें):", value="SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST.")
save_all_rules_btn = gr.Button("💾 Save All Rules & Sync to Google Sheet", variant="primary", size="lg")
save_status_output = gr.Textbox(label="Sync Status", interactive=False)
# 🔄 Live Data Populator when Shipper is selected in Admin
def on_admin_shipper_change(shipper_name):
if not shipper_name:
return "", [], [], "parser_welspun", "LUT, UNDER LUT, UNDER BOND", "SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST."
sheet_data = fetch_all_from_sheet()
shippers_dict = sheet_data.get("shippers", {}) if sheet_data else {}
s_info = shippers_dict.get(shipper_name, {})
# 1. Template Status
has_tpl = load_template_bytes_from_sheet(shipper_name) is not None
tpl_msg = "✅ Template Excel File is Uploaded & Saved in Google Sheet." if has_tpl else "❌ No template uploaded for this shipper."
# 2. Header Rules Table Data
mapping_rules = s_info.get("mapping_rules", {})
h_rows = []
for f_name, f_val in mapping_rules.items():
h_rows.append([
f_name,
f_val.get("logic", "Main Invoice"),
f_val.get("keyword", ""),
f_val.get("cell", ""),
f_val.get("ai_prompt", ""),
f_val.get("result_example", "")
])
if not h_rows:
h_rows = [["", "", "", "", "", ""]]
# 3. Item Table Rules Data
item_rules = s_info.get("item_table_rules", {})
i_rows = []
for i_name, i_val in item_rules.items():
i_rows.append([
i_name,
i_val.get("col", ""),
i_val.get("type", "PDF Row Item"),
i_val.get("rule", ""),
i_val.get("result_example", "")
])
if not i_rows:
i_rows = [["", "", "", "", ""]]
# 4. Parser Name
p_name = s_info.get("item_table_rule_name", "parser_welspun")
# 5. IGST Config
igst_cfg = s_info.get("igst_config", {})
lut_kw = igst_cfg.get("lut_keywords", "LUT, UNDER LUT, UNDER BOND")
paid_kw = igst_cfg.get("paid_keywords", "SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST.")
return tpl_msg, h_rows, i_rows, p_name, lut_kw, paid_kw
admin_shipper_dropdown.change(
fn=on_admin_shipper_change,
inputs=admin_shipper_dropdown,
outputs=[tpl_status_box, header_rules_table, item_rules_table, parser_selector, lut_keywords_input, paid_keywords_input]
)
# Event Handlers & Initial Data Load
def update_admin_shippers():
names_update = fetch_shipper_names()
return names_update, names_update
demo.load(fn=update_admin_shippers, outputs=[shipper_dropdown, admin_shipper_dropdown])
unlock_btn.click(fn=verify_password, inputs=pass_input, outputs=[login_screen, main_app])
pass_input.submit(fn=verify_password, inputs=pass_input, outputs=[login_screen, main_app])
admin_login_btn.click(fn=verify_admin, inputs=admin_pwd_input, outputs=[user_dashboard, admin_dashboard])
admin_back_btn.click(fn=lambda: (gr.update(visible=True), gr.update(visible=False)), outputs=[user_dashboard, admin_dashboard])
if __name__ == "__main__":
demo.launch()