import os import json import pdfplumber import io from io import BytesIO import re import gradio as gr from pdf_engine import detect_igst_status, extract_header_value from google_sheet_sync import ( fetch_all_from_sheet, push_rules_to_sheet, push_template_file_to_sheet, load_template_bytes_from_sheet ) import requests LOCAL_DATA_DIR = "local_shipper_data" TEMPLATES_DIR = os.path.join(LOCAL_DATA_DIR, "templates") def ensure_local_directories(): if not os.path.exists(LOCAL_DATA_DIR): os.makedirs(LOCAL_DATA_DIR) if not os.path.exists(TEMPLATES_DIR): os.makedirs(TEMPLATES_DIR) def fetch_data_from_google_sheet(): try: data = fetch_all_from_sheet() if isinstance(data, dict) and data: return data except Exception as e: pass return {} def load_shipper_database(): ensure_local_directories() sheet_data = fetch_data_from_google_sheet() shippers_dict = sheet_data.get("shippers", {}) if isinstance(sheet_data, dict) else {} if shippers_dict: return shippers_dict else: json_path = os.path.join(LOCAL_DATA_DIR, "shippers_rules.json") if os.path.exists(json_path): try: with open(json_path, "r", encoding="utf-8") as f: saved_data = json.load(f) if isinstance(saved_data, dict) and saved_data: return saved_data except Exception: pass return {} def save_shipper_database_to_sheet(shipper_database): ensure_local_directories() json_path = os.path.join(LOCAL_DATA_DIR, "shippers_rules.json") try: with open(json_path, "w", encoding="utf-8") as f: json.dump(shipper_database, f, indent=4) shippers_payload = {} for s_name, s_data in shipper_database.items(): shippers_payload[s_name] = { "mapping_rules": s_data.get("mapping_rules", {}), "item_table_rules": s_data.get("item_table_rules", {}), "item_table_rule_name": s_data.get("item_table_rule_name", "parser_welspun"), "igst_config": s_data.get("igst_config", {}) } push_rules_to_sheet(shippers_payload) return True, "ЁЯОЙ Rules successfully saved and synced to Google Sheet!" except Exception as e: return False, f"Save Error: {str(e)}" def render_shipper_data(): db = load_shipper_database() shippers_list = sorted(list(db.keys())) gr.Markdown("### ЁЯПв Add Shipper Name & No-Code Visual Mapping Builder") gr.Markdown("рдХреАрд╡рд░реНрдб, рдмреЙрдХреНрд╕ рдПрдХреНрд╕рдЯреНрд░реИрдХреНрд╢рди рдФрд░ рдкреЛрдЬреАрд╢рди рдХреЗ рдЬрд░рд┐рдП рд╕реНрдорд╛рд░реНрдЯ рдЯреЗрд╕реНрдЯ рдФрд░ рд╕реЗрд╡ рдЯреВрд▓ред") # 1. Shipper Selection Dropdown (Jab tak select na ho, niche ka kuch nahi dikhega) selected_shipper_dropdown = gr.Dropdown( choices=shippers_list, label="1. рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╢рд┐рдкрд░ рдЪреБрдиреЗрдВ:", value=None, interactive=True ) # Config Section Wrapper (Initially Hidden) with gr.Column(visible=False) as config_container: shipper_title_md = gr.Markdown("### тЪЩя╕П рд╢рд┐рдкрд░ рдкреНрд░реЛрдлрд╛рдЗрд▓:") # ЁЯУБ 1. Template File Section gr.Markdown("---") gr.Markdown("### ЁЯУБ 1. рдЯреЗрдореНрдкрд▓реЗрдЯ рдлрд╝рд╛рдЗрд▓ рдЕрдкрд▓реЛрдб (Full Job Excel Template)") with gr.Row() as tpl_upload_row: tpl_file_input = gr.File(label="Blank Full Job Excel Format File (Template) рдЪреБрдиреЗрдВ", file_types=[".xlsx", ".xls"]) save_tpl_btn = gr.Button("ЁЯЪА Save Template to Google Sheet", variant="primary") with gr.Row(visible=False) as tpl_status_row: tpl_status_txt = gr.Textbox(label="Template Status", interactive=False, scale=3) replace_tpl_file = gr.File(label="ЁЯФД Replace Template", file_types=[".xlsx", ".xls"], scale=2) confirm_replace_btn = gr.Button("ЁЯЪА Confirm & Replace", variant="secondary") delete_tpl_btn = gr.Button("ЁЯЧСя╕П Delete Template", variant="stop") tpl_action_msg = gr.Textbox(label="Status Message", interactive=False) # ЁЯзк 2. Sample PDF Upload & Text Inspector gr.Markdown("---") gr.Markdown("### ЁЯзк 2. Sample PDF Upload & Text Viewer") with gr.Row(): test_pdf_input = gr.File(label="рдЯреЗрд╕реНрдЯ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рд╕реИрдВрдкрд▓ рдЗрдирд╡реЙрдЗрд╕ PDF рдЕрдкрд▓реЛрдб рдХрд░реЗрдВ", file_types=[".pdf"], scale=1) pdf_preview_box = gr.Textbox(label="Extracted PDF Text Preview", lines=4, scale=2, interactive=False) # тЪб 3. Smart Test & Save Generator gr.Markdown("---") gr.Markdown("### тЪб 3. Smart Test & Save Generator (Box & Position)") with gr.Row(): target_val_box = gr.Textbox(label="1. рдЯрд╛рд░рдЧреЗрдЯ рд╡реИрд▓реНрдпреВ:") keyword_box = gr.Textbox(label="2. рдореБрдЦреНрдп рдХреАрд╡рд░реНрдб:") direction_radio = gr.Radio(["Right (рдЖрдЧреЗ)", "Below (рдиреАрдЪреЗ)", "ЁЯУж Extract Inside Box (рдбрдмреНрдмрд╛)"], label="3. рджрд┐рд╢рд╛ / рддрд░реАрдХрд╛:", value="Right (рдЖрдЧреЗ)") offset_num = gr.Number(value=1, label="4. Index:") test_run_btn = gr.Button("ЁЯзк Test Extraction First", variant="secondary") test_output_box = gr.Textbox(label="Test Result", interactive=False) target_field_dropdown = gr.Dropdown(choices=[], label="5. рдпрд╣ рд▓реЙрдЬрд┐рдХ рдХрд┐рд╕ рд╣реЗрдбрд░ рдлреАрд▓реНрдб рдкрд░ рд╕реЗрд╡ рдХрд░рдирд╛ рд╣реИ?", interactive=True) save_tested_rule_btn = gr.Button("ЁЯТ╛ Confirm & Save to Shipper Rule", variant="primary") # ЁЯЫая╕П 4. Header Fields Mapping Rules Table gr.Markdown("---") gr.Markdown("### ЁЯЫая╕П 4. Header Fields Mapping & Regex Rules") header_rules_dataframe = gr.Dataframe( headers=["Field Name", "Source Doc", "Keyword", "Cell", "Prompt", "Result Ex", "Local Python/Regex Logic"], datatype=["str", "str", "str", "str", "str", "str", "str"], row_count=5, col_count=7, interactive=True, label="Header Mapping Rules" ) # ЁЯУЛ 5. Dynamic Item Table Rules & Mapping gr.Markdown("---") gr.Markdown("### ЁЯУЛ 5. Dynamic Item Table Rules & Mapping") parser_dropdown = gr.Dropdown(["parser_welspun", "parser_polycab", "parser_bkt", "parser_vapi_welspun"], label="рдЗрд╕ рд╢рд┐рдкрд░ рдХреЗ рд▓рд┐рдП рдЖрдЗрдЯрдо рдкрд╛рд░реНрд╕рд░ рдЪреБрдиреЗрдВ:") item_rules_dataframe = 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, interactive=True, label="Item Table Rules" ) # тЪЩя╕П 6. IGST & Lut Configuration gr.Markdown("---") gr.Markdown("### тЪЩя╕П 6. IGST & Lut Configuration") with gr.Row(): lut_kws_box = gr.Textbox(label="LUT Keywords (рдХреЙрдорд╛ рд╕реЗ рдЕрд▓рдЧ рдХрд░реЗрдВ):") paid_kws_box = gr.Textbox(label="Paid Keywords (рдХреЙрдорд╛ рд╕реЗ рдЕрд▓рдЧ рдХрд░реЗрдВ):") save_all_btn = gr.Button("ЁЯТ╛ Save All Rules & Sync to Google Sheet", variant="primary", size="lg") sync_status_box = gr.Textbox(label="Sync Status", interactive=False) # --- Event Handlers & Dynamic Binding --- def on_shipper_select(shipper_name): if not shipper_name: return ( gr.update(visible=False), "", gr.update(visible=True), gr.update(visible=False), "", [], [], "parser_welspun", "", "", gr.update(choices=[]) ) current_db = load_shipper_database() s_info = current_db.get(shipper_name, {}) # Template Check t_bytes = load_template_bytes_from_sheet(shipper_name) has_tpl = t_bytes is not None and len(t_bytes) > 0 tpl_msg = f"тЬЕ 'Full Job Excel Format File (Template)' рдЧреВрдЧрд▓ рд╢реАрдЯ рдкрд░ рдЕрдкрд▓реЛрдбреЗрдб рдПрд╡рдВ рд╕реБрд░рдХреНрд╖рд┐рдд рд╣реИред" if has_tpl else "тД╣я╕П рдЗрд╕ рд╢рд┐рдкрд░ рдХреЗ рд▓рд┐рдП рдЕрднреА рдХреЛрдИ рдЯреЗрдореНрдкрд▓реЗрдЯ рдЕрдкрд▓реЛрдб рдирд╣реАрдВ рдХреА рдЧрдИ рд╣реИред" # Header Rules Rows mapping_rules = s_info.get("mapping_rules", {}) h_rows = [[f, v.get("logic", "Main Invoice"), v.get("keyword", ""), v.get("cell", ""), v.get("ai_prompt", ""), v.get("result_example", ""), v.get("extracted_logic", "")] for f, v in mapping_rules.items()] if not h_rows: h_rows = [["", "Main Invoice", "", "", "", "", ""]] # Item Rules Rows item_rules = s_info.get("item_table_rules", {}) i_rows = [[i, v.get("col", "K"), v.get("type", "PDF Row Item"), v.get("rule", ""), v.get("result_example", "")] for i, v in item_rules.items()] if not i_rows: i_rows = [["", "K", "PDF Row Item", "", ""]] parser_name = s_info.get("item_table_rule_name", "parser_welspun") igst_cfg = s_info.get("igst_config", {}) lut_val = igst_cfg.get("lut_keywords", "LUT, UNDER LUT, UNDER BOND") paid_val = igst_cfg.get("paid_keywords", "SUPPLY MEANT FOR EXPORT ON PAYMENT OF IGST.") field_choices = list(mapping_rules.keys()) return ( gr.update(visible=True), f"### тЪЩя╕П рд╢рд┐рдкрд░ рдкреНрд░реЛрдлрд╛рдЗрд▓: **{shipper_name}**", gr.update(visible=not has_tpl), gr.update(visible=has_tpl), tpl_msg, h_rows, i_rows, parser_name, lut_val, paid_val, gr.update(choices=field_choices, value=field_choices[0] if field_choices else None) ) selected_shipper_dropdown.change( fn=on_shipper_select, inputs=selected_shipper_dropdown, outputs=[ config_container, shipper_title_md, tpl_upload_row, tpl_status_row, tpl_status_txt, header_rules_dataframe, item_rules_dataframe, parser_dropdown, lut_kws_box, paid_kws_box, target_field_dropdown ] ) # Template Save & Replace Handlers def save_tpl_action(shipper_name, file_obj): if not shipper_name or not file_obj: return "тЭМ рдХреГрдкрдпрд╛ рд╢рд┐рдкрд░ рдЪреБрдиреЗрдВ рдФрд░ рдлрд╛рдЗрд▓ рдЕрдкрд▓реЛрдб рдХрд░реЗрдВ!" with open(file_obj.name, "rb") as f: success = push_template_file_to_sheet(shipper_name, f.read()) return "ЁЯОЙ рдЯреЗрдореНрдкрд▓реЗрдЯ рд╕рдлрд▓рддрд╛рдкреВрд░реНрд╡рдХ рдЧреВрдЧрд▓ рд╢реАрдЯ рдкрд░ рд╕реЗрд╡ рд╣реЛ рдЧрдИ!" if success else "тЭМ рдЕрдкрд▓реЛрдб рдлреЗрд▓ рд╣реЛ рдЧрдпрд╛!" save_tpl_btn.click(fn=save_tpl_action, inputs=[selected_shipper_dropdown, tpl_file_input], outputs=tpl_action_msg) confirm_replace_btn.click(fn=save_tpl_action, inputs=[selected_shipper_dropdown, replace_tpl_file], outputs=tpl_action_msg) def delete_tpl_action(shipper_name): if not shipper_name: return "тЭМ рд╢рд┐рдкрд░ рдирд╣реАрдВ рдЪреБрдирд╛ рдЧрдпрд╛ред" push_template_file_to_sheet(shipper_name, b"") return "ЁЯЧСя╕П рдЯреЗрдореНрдкрд▓реЗрдЯ рдбрд┐рд▓реАрдЯ рд╣реЛ рдЧрдИ!" delete_tpl_btn.click(fn=delete_tpl_action, inputs=[selected_shipper_dropdown], outputs=tpl_action_msg) # Save All Rules Handler def save_all_rules(shipper_name, h_data, i_data, parser_val, lut_val, paid_val): if not shipper_name: return "тЭМ рд╢рд┐рдкрд░ рдирд╣реАрдВ рдЪреБрдирд╛ рдЧрдпрд╛ред" db = load_shipper_database() s_info = db.setdefault(shipper_name, {}) # Map Header Rules new_mapping = {} for row in h_data: if row and row[0]: new_mapping[row[0]] = { "logic": row[1], "keyword": row[2], "cell": row[3], "ai_prompt": row[4], "result_example": row[5], "extracted_logic": row[6] } s_info["mapping_rules"] = new_mapping # Map Item Rules new_items = {} for row in i_data: if row and row[0]: new_items[row[0]] = { "col": row[1], "type": row[2], "rule": row[3], "result_example": row[4] } s_info["item_table_rules"] = new_items s_info["item_table_rule_name"] = parser_val s_info["igst_config"] = {"lut_keywords": lut_val, "paid_keywords": paid_val} _, msg = save_shipper_database_to_sheet(db) return msg save_all_btn.click( fn=save_all_rules, inputs=[selected_shipper_dropdown, header_rules_dataframe, item_rules_dataframe, parser_dropdown, lut_kws_box, paid_kws_box], outputs=sync_status_box )