joschetan commited on
Commit
b40c854
·
verified ·
1 Parent(s): 11f8f1a

Update processor.py

Browse files
Files changed (1) hide show
  1. processor.py +163 -270
processor.py CHANGED
@@ -1,4 +1,3 @@
1
- import streamlit as st
2
  import openpyxl
3
  import pdfplumber
4
  import re
@@ -10,289 +9,183 @@ from parser_bkt import extract_bkt_items
10
  from parser_polycab import extract_polycab_items, map_polycab_items_to_excel_dynamic
11
  from parser_vapi_welspun import extract_vapi_welspun_items, map_vapi_welspun_items_to_excel_dynamic
12
 
13
- from shipper_data import fetch_data_from_google_sheet, ensure_default_shipper
14
  from pdf_engine import apply_rule_filter, extract_header_value
15
  from google_sheet_sync import load_template_from_sheet
16
  from supporting_engine import extract_data_from_supporting_file
17
 
18
- def render_processor():
19
- fetch_data_from_google_sheet()
20
- ensure_default_shipper()
 
 
 
21
 
22
- st.header("📤 Invoice Processing Zone (Multi-Document)")
23
- st.caption("इनवॉइस PDF या Excel के साथ-साथ GST Invoice और DEEC Declaration अपलोड करने और पर्टिकुलर सेल/कॉलम में भेजने का ज़ोन.")
24
-
25
- shippers_list = sorted(list(st.session_state["shipper_database"].keys()))
26
-
27
- if shippers_list:
28
- selected_shipper = st.selectbox(
29
- "किस शिपर का इनवॉइस प्रोसेस करना है?",
30
- shippers_list,
31
- index=None,
32
- placeholder="शिपर का नाम टाइप करें या चुनें..."
33
- )
34
 
35
- if selected_shipper:
36
- shipper_info = st.session_state["shipper_database"][selected_shipper]
37
-
38
- if f"inv_count_{selected_shipper}" not in st.session_state:
39
- st.session_state[f"inv_count_{selected_shipper}"] = 1
40
-
41
- inv_count = st.session_state[f"inv_count_{selected_shipper}"]
42
-
43
- st.subheader("📑 Upload Invoices & Supporting Documents")
44
 
45
- uploaded_batches = []
46
- for i in range(inv_count):
47
- st.markdown(f"#### ➡️ Invoice Set #{i+1}")
48
- col_inv, col_gst, col_deec = st.columns(3)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- with col_inv:
51
- pdf_f = st.file_uploader(f" मुख्य इनवॉइस (PDF / Excel) #{i+1}", type=["pdf", "xlsx", "xls"], key=f"inv_pdf_{selected_shipper}_{i}")
52
- with col_gst:
53
- gst_f = st.file_uploader(f" GST Invoice #{i+1} (PDF/Excel)", type=["pdf", "xlsx", "xls"], key=f"gst_file_{selected_shipper}_{i}")
54
- with col_deec:
55
- deec_f = st.file_uploader(f" DEEC Decl. #{i+1} (PDF/Excel)", type=["pdf", "xlsx", "xls"], key=f"deec_file_{selected_shipper}_{i}")
56
-
57
- uploaded_batches.append((i+1, pdf_f, gst_f, deec_f))
58
- st.write("---")
59
-
60
- col_b1, col_b2, col_space = st.columns([2, 2, 6])
61
- with col_b1:
62
- if inv_count < 10:
63
- if st.button("➕ Add Invoice Set", key=f"add_btn_{selected_shipper}", use_container_width=True):
64
- st.session_state[f"inv_count_{selected_shipper}"] += 1
65
- st.rerun()
66
- with col_b2:
67
- if inv_count > 1:
68
- if st.button("➖ Remove Last", key=f"rem_btn_{selected_shipper}", use_container_width=True):
69
- st.session_state[f"inv_count_{selected_shipper}"] -= 1
70
- st.rerun()
71
-
72
- st.write("---")
73
-
74
- valid_batches = [b for b in uploaded_batches if b[1] is not None]
75
 
76
- if valid_batches and st.button("🚀 Process & Generate Excel with Supporting Docs", type="primary", use_container_width=True):
77
- with st.spinner(f"कुल {len(valid_batches)} इनवॉइस सेट प्रोसेस हो रहे हैं..."):
78
- rules = shipper_info.get("mapping_rules", {})
79
- item_table_rules = shipper_info.get("item_table_rules", {})
80
-
81
- assigned_parser = shipper_info.get("item_table_rule_name", "parser_welspun").strip().lower()
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- igst_cfg = shipper_info.get("igst_config", {})
84
- lut_kws = igst_cfg.get("lut_keywords", "")
85
- paid_kws = igst_cfg.get("paid_keywords", "")
 
 
 
86
 
87
- wb = load_template_from_sheet(selected_shipper)
88
- if wb is None:
89
- wb = openpyxl.Workbook()
90
-
91
- ws = wb["INV"] if "INV" in wb.sheetnames else wb.active
 
 
 
 
 
 
 
 
 
92
 
93
- first_inv_no = "INV"
94
- overall_item_sr = 1
95
- excel_write_row = 2
 
96
 
97
- for inv_sr_number, inv_file, gst_file, deec_file in valid_batches:
98
- pdf_text = ""
99
- pdf_lines = []
100
-
101
- if inv_file:
102
- file_bytes_cache = inv_file.getvalue()
103
- st.session_state["cached_pdf_bytes"] = file_bytes_cache
104
-
105
- file_name_lower = inv_file.name.lower()
106
- if file_name_lower.endswith(".pdf"):
107
- with pdfplumber.open(BytesIO(st.session_state["cached_pdf_bytes"])) as pdf:
108
- for page in pdf.pages:
109
- t = page.extract_text()
110
- if t:
111
- pdf_text += t + "\n"
112
- pdf_lines.extend(t.split("\n"))
113
- else:
114
- excel_text, _ = extract_data_from_supporting_file(inv_file)
115
- if excel_text:
116
- pdf_text = excel_text
117
- pdf_lines = excel_text.split("\n")
118
-
119
- gst_text, _ = extract_data_from_supporting_file(gst_file) if gst_file else ("", None)
120
- deec_text, _ = extract_data_from_supporting_file(deec_file) if deec_file else ("", None)
121
-
122
- current_inv_number = f"INV_{inv_sr_number}"
123
- current_inv_date = ""
124
- inv_data_dict = {}
125
-
126
- summary_row = 1 + inv_sr_number
127
-
128
- for field, r_info in rules.items():
129
- kw = r_info.get("keyword", "").strip()
130
- if kw.startswith("'") and len(kw) > 1:
131
- kw = kw[1:].strip()
132
-
133
- pos = r_info.get("position", "Right (आगे)")
134
- target_cell = r_info.get("cell", "").strip().upper()
135
- mode = r_info.get("match_mode", "Exact Word")
136
- stop_kw = r_info.get("stop_kw", "").strip()
137
- flt = r_info.get("filter", "None")
138
- fallback_val = r_info.get("fallback", "").strip()
139
- doc_source = r_info.get("logic", "Main Invoice")
140
-
141
- extracted_logic = r_info.get("extracted_logic", "").strip()
142
- found_val = None
143
-
144
- target_lines, target_full_text = pdf_lines, pdf_text
145
- if "gst" in doc_source.lower() and gst_file:
146
- target_lines = gst_text.split("\n")
147
- target_full_text = gst_text
148
- elif "deec" in doc_source.lower() and deec_file:
149
- target_lines = deec_text.split("\n")
150
- target_full_text = deec_text
151
-
152
- # 🚀 मजबूत और क्लीन रेजेक्स एक्सेक्यूशन (Markdown और एस्केप न्यूलाइन फिक्स के साथ)
153
- if extracted_logic:
154
- try:
155
- clean_code = extracted_logic.replace("```python", "").replace("```", "").strip()
156
- clean_code = clean_code.replace(r"\n", "\n") # एस्केप न्यूलाइन को ठीक करना
157
- local_vars = {"text": target_full_text, "lines": target_lines, "re": re}
158
- exec(clean_code, {}, local_vars)
159
- found_val = local_vars.get("value", None)
160
- except Exception as e:
161
- found_val = None
162
-
163
- if not found_val or not str(found_val).strip():
164
- pdf_bytes = st.session_state.get("cached_pdf_bytes", None)
165
- found_val = extract_header_value(target_lines, target_full_text, kw, pos, mode, stop_kw, flt, field_label=field, pdf_bytes=pdf_bytes)
166
-
167
- if not found_val or not str(found_val).strip():
168
- if fallback_val:
169
- found_val = fallback_val
170
-
171
- inv_data_dict[field.lower()] = found_val
172
-
173
- if target_cell and "dynamic" not in target_cell.lower():
174
- try:
175
- if "\n" in str(found_val):
176
- col_letters = re.findall(r'[A-Za-z]+', target_cell)[0].upper()
177
- start_row_num = int(re.findall(r'\d+', target_cell)[0]) if re.findall(r'\d+', target_cell) else summary_row
178
-
179
- lines = str(found_val).split("\n")
180
- for idx, line_val in enumerate(lines):
181
- current_row = start_row_num + idx
182
- ws[f"{col_letters}{current_row}"] = line_val.strip()
183
- else:
184
- if target_cell.isalpha():
185
- cell_to_write = f"{target_cell}{summary_row}"
186
- else:
187
- cell_to_write = target_cell
188
- ws[cell_to_write] = found_val
189
- except Exception:
190
- pass
191
-
192
- if "inv. no" in field.lower() or "invoice no" in field.lower():
193
- if found_val:
194
- current_inv_number = found_val
195
- if inv_sr_number == 1: first_inv_no = found_val
196
-
197
- if "date" in field.lower() or "dt" in field.lower():
198
- d_match = re.search(r'\b\d{2}[./-]\d{2}[./-]\d{4}\b', str(found_val))
199
- if d_match:
200
- current_inv_date = d_match.group(0).replace(".", "/").replace("-", "/")
201
- elif found_val and not str(found_val).lower().startswith("inv"):
202
- current_inv_date = found_val
203
-
204
- ws[f"AH{summary_row}"] = inv_sr_number
205
- ws[f"AI{summary_row}"] = current_inv_number
206
-
207
- if current_inv_date:
208
- ws[f"AJ{summary_row}"] = current_inv_date
209
 
210
- resolved_item_rules = {}
211
- for i_name, i_info in item_table_rules.items():
212
- i_type = i_info.get("type", "")
213
- i_rule = i_info.get("rule", "")
214
- if i_rule.startswith("'") and len(i_rule) > 1:
215
- i_rule = i_rule[1:].strip()
216
- i_col = i_info.get("col", "K")
217
-
218
- actual_rule_val = i_rule
219
- if i_type == "Header Field Mapping":
220
- matched_header_key = i_rule.lower()
221
- if matched_header_key in inv_data_dict:
222
- actual_rule_val = inv_data_dict[matched_header_key]
223
-
224
- resolved_item_rules[i_name] = {
225
- "col": i_col,
226
- "type": i_type if i_type != "Header Field Mapping" else "Constant Text",
227
- "rule": actual_rule_val
228
- }
229
 
230
- if assigned_parser == "parser_bkt":
231
- parsed_items = extract_bkt_items(pdf_lines)
232
- elif assigned_parser == "parser_polycab":
233
- parsed_items = extract_polycab_items(pdf_lines, pdf_text=pdf_text)
234
- elif assigned_parser == "parser_vapi_welspun":
235
- parsed_items = extract_vapi_welspun_items(pdf_lines, pdf_text=pdf_text)
236
- elif assigned_parser == "parser_welspun":
237
- parsed_items = extract_welspun_items(pdf_lines, pdf_text=pdf_text)
238
- else:
239
- parsed_items = extract_welspun_items(pdf_lines, pdf_text=pdf_text)
240
-
241
- if assigned_parser == "parser_polycab":
242
- ws, overall_item_sr, excel_write_row = map_polycab_items_to_excel_dynamic(
243
- ws, parsed_items, resolved_item_rules,
244
- inv_sr_no=inv_sr_number,
245
- start_overall_sr=overall_item_sr,
246
- start_excel_row=excel_write_row,
247
- default_invoice_no=current_inv_number,
248
- default_invoice_date=current_inv_date,
249
- pdf_text=pdf_text,
250
- lut_kws=lut_kws,
251
- paid_kws=paid_kws,
252
- parser_rule=assigned_parser
253
- )
254
- elif assigned_parser == "parser_vapi_welspun":
255
- ws, overall_item_sr, excel_write_row = map_vapi_welspun_items_to_excel_dynamic(
256
- ws, parsed_items, resolved_item_rules,
257
- inv_sr_no=inv_sr_number,
258
- start_overall_sr=overall_item_sr,
259
- start_excel_row=excel_write_row,
260
- default_invoice_no=current_inv_number,
261
- default_invoice_date=current_inv_date,
262
- pdf_text=pdf_text,
263
- lut_kws=lut_kws,
264
- paid_kws=paid_kws,
265
- parser_rule=assigned_parser
266
- )
267
- else:
268
- ws, overall_item_sr, excel_write_row = map_items_to_excel_dynamic(
269
- ws, parsed_items, resolved_item_rules,
270
- inv_sr_no=inv_sr_number,
271
- start_overall_sr=overall_item_sr,
272
- start_excel_row=excel_write_row,
273
- default_invoice_no=current_inv_number,
274
- default_invoice_date=current_inv_date,
275
- pdf_text=pdf_text,
276
- lut_kws=lut_kws,
277
- paid_kws=paid_kws,
278
- parser_rule=assigned_parser
279
- )
280
 
281
- output = BytesIO()
282
- wb.save(output)
283
-
284
- short_shipper = selected_shipper.split(" ")[0].lower()
285
- clean_inv = re.sub(r'[\\/*?:"<>|]', "", first_inv_no)
286
- final_filename = f"{clean_inv}_{short_shipper}_MultiDoc.xlsx"
287
-
288
- st.session_state["processed_file_ready"] = {"filename": final_filename, "data": output.getvalue()}
289
- st.success(f"🎉 सफलता! सपोर्टिंग डॉक्यूमेंट्स के साथ फाइल '{final_filename}' तैयार है!")
290
 
291
- if st.session_state.get("processed_file_ready", None):
292
- st.download_button(
293
- label=f"📥 {st.session_state['processed_file_ready']['filename']} डाउनलोड करें",
294
- data=st.session_state['processed_file_ready']['data'],
295
- file_name=st.session_state['processed_file_ready']['filename'],
296
- mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
297
- )
298
- st.session_state["processed_file_ready"] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import openpyxl
2
  import pdfplumber
3
  import re
 
9
  from parser_polycab import extract_polycab_items, map_polycab_items_to_excel_dynamic
10
  from parser_vapi_welspun import extract_vapi_welspun_items, map_vapi_welspun_items_to_excel_dynamic
11
 
 
12
  from pdf_engine import apply_rule_filter, extract_header_value
13
  from google_sheet_sync import load_template_from_sheet
14
  from supporting_engine import extract_data_from_supporting_file
15
 
16
+ def process_invoices_backend(selected_shipper, shipper_info, main_inv_file, gst_inv_file, deec_decl_file):
17
+ """
18
+ Gradio ke file objects ko process karke Excel file ki bytes return karta hai.
19
+ """
20
+ if main_inv_file is None:
21
+ return None, "⚠️ Mukhya invoice file missing hai!"
22
 
23
+ try:
24
+ rules = shipper_info.get("mapping_rules", {})
25
+ item_table_rules = shipper_info.get("item_table_rules", {})
26
+ assigned_parser = shipper_info.get("item_table_rule_name", "parser_welspun").strip().lower()
 
 
 
 
 
 
 
 
27
 
28
+ igst_cfg = shipper_info.get("igst_config", {})
29
+ lut_kws = igst_cfg.get("lut_keywords", "")
30
+ paid_kws = igst_cfg.get("paid_keywords", "")
31
+
32
+ wb = load_template_from_sheet(selected_shipper)
33
+ if wb is None:
34
+ wb = openpyxl.Workbook()
 
 
35
 
36
+ ws = wb["INV"] if "INV" in wb.sheetnames else wb.active
37
+
38
+ first_inv_no = "INV"
39
+ overall_item_sr = 1
40
+ excel_write_row = 2
41
+
42
+ # Gradio file object ka path ya bytes handle karna
43
+ file_bytes = main_inv_file.read() if hasattr(main_inv_file, "read") else open(main_inv_file, "rb").read()
44
+ file_name = getattr(main_inv_file, "name", "invoice.pdf")
45
+
46
+ pdf_text = ""
47
+ pdf_lines = []
48
+
49
+ if file_name.lower().endswith(".pdf"):
50
+ with pdfplumber.open(BytesIO(file_bytes)) as pdf:
51
+ for page in pdf.pages:
52
+ t = page.extract_text()
53
+ if t:
54
+ pdf_text += t + "\n"
55
+ pdf_lines.extend(t.split("\n"))
56
+ else:
57
+ excel_text, _ = extract_data_from_supporting_file(main_inv_file)
58
+ if excel_text:
59
+ pdf_text = excel_text
60
+ pdf_lines = excel_text.split("\n")
61
 
62
+ gst_text, _ = extract_data_from_supporting_file(gst_inv_file) if gst_inv_file else ("", None)
63
+ deec_text, _ = extract_data_from_supporting_file(deec_decl_file) if deec_decl_file else ("", None)
64
+
65
+ current_inv_number = "INV_1"
66
+ current_inv_date = ""
67
+ inv_data_dict = {}
68
+ summary_row = 2
69
+
70
+ for field, r_info in rules.items():
71
+ kw = r_info.get("keyword", "").strip()
72
+ if kw.startswith("'") and len(kw) > 1:
73
+ kw = kw[1:].strip()
74
+
75
+ pos = r_info.get("position", "Right (आगे)")
76
+ target_cell = r_info.get("cell", "").strip().upper()
77
+ mode = r_info.get("match_mode", "Exact Word")
78
+ stop_kw = r_info.get("stop_kw", "").strip()
79
+ flt = r_info.get("filter", "None")
80
+ fallback_val = r_info.get("fallback", "").strip()
81
+ doc_source = r_info.get("logic", "Main Invoice")
82
+ extracted_logic = r_info.get("extracted_logic", "").strip()
83
+ found_val = None
 
 
 
84
 
85
+ target_lines, target_full_text = pdf_lines, pdf_text
86
+ if "gst" in doc_source.lower() and gst_inv_file:
87
+ target_lines = gst_text.split("\n")
88
+ target_full_text = gst_text
89
+ elif "deec" in doc_source.lower() and deec_decl_file:
90
+ target_lines = deec_text.split("\n")
91
+ target_full_text = deec_text
92
+
93
+ if extracted_logic:
94
+ try:
95
+ clean_code = extracted_logic.replace("```python", "").replace("```", "").strip()
96
+ clean_code = clean_code.replace(r"\n", "\n")
97
+ local_vars = {"text": target_full_text, "lines": target_lines, "re": re}
98
+ exec(clean_code, {}, local_vars)
99
+ found_val = local_vars.get("value", None)
100
+ except Exception:
101
+ found_val = None
102
 
103
+ if not found_val or not str(found_val).strip():
104
+ found_val = extract_header_value(target_lines, target_full_text, kw, pos, mode, stop_kw, flt, field_label=field, pdf_bytes=file_bytes)
105
+
106
+ if not found_val or not str(found_val).strip():
107
+ if fallback_val:
108
+ found_val = fallback_val
109
 
110
+ inv_data_dict[field.lower()] = found_val
111
+
112
+ if target_cell and "dynamic" not in target_cell.lower():
113
+ try:
114
+ if "\n" in str(found_val):
115
+ col_letters = re.findall(r'[A-Za-z]+', target_cell)[0].upper()
116
+ start_row_num = int(re.findall(r'\d+', target_cell)[0]) if re.findall(r'\d+', target_cell) else summary_row
117
+ for idx, line_val in enumerate(str(found_val).split("\n")):
118
+ ws[f"{col_letters}{start_row_num + idx}"] = line_val.strip()
119
+ else:
120
+ cell_to_write = f"{target_cell}{summary_row}" if target_cell.isalpha() else target_cell
121
+ ws[cell_to_write] = found_val
122
+ except Exception:
123
+ pass
124
 
125
+ if "inv. no" in field.lower() or "invoice no" in field.lower():
126
+ if found_val:
127
+ current_inv_number = found_val
128
+ first_inv_no = found_val
129
 
130
+ if "date" in field.lower() or "dt" in field.lower():
131
+ d_match = re.search(r'\b\d{2}[./-]\d{2}[./-]\d{4}\b', str(found_val))
132
+ if d_match:
133
+ current_inv_date = d_match.group(0).replace(".", "/").replace("-", "/")
134
+ elif found_val and not str(found_val).lower().startswith("inv"):
135
+ current_inv_date = found_val
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
 
137
+ ws[f"AH{summary_row}"] = 1
138
+ ws[f"AI{summary_row}"] = current_inv_number
139
+ if current_inv_date:
140
+ ws[f"AJ{summary_row}"] = current_inv_date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
+ resolved_item_rules = {}
143
+ for i_name, i_info in item_table_rules.items():
144
+ i_type = i_info.get("type", "")
145
+ i_rule = i_info.get("rule", "")
146
+ if i_rule.startswith("'") and len(i_rule) > 1:
147
+ i_rule = i_rule[1:].strip()
148
+ i_col = i_info.get("col", "K")
149
+
150
+ actual_rule_val = i_rule
151
+ if i_type == "Header Field Mapping" and i_rule.lower() in inv_data_dict:
152
+ actual_rule_val = inv_data_dict[i_rule.lower()]
153
+
154
+ resolved_item_rules[i_name] = {
155
+ "col": i_col,
156
+ "type": i_type if i_type != "Header Field Mapping" else "Constant Text",
157
+ "rule": actual_rule_val
158
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ if assigned_parser == "parser_bkt":
161
+ parsed_items = extract_bkt_items(pdf_lines)
162
+ elif assigned_parser == "parser_polycab":
163
+ parsed_items = extract_polycab_items(pdf_lines, pdf_text=pdf_text)
164
+ elif assigned_parser == "parser_vapi_welspun":
165
+ parsed_items = extract_vapi_welspun_items(pdf_lines, pdf_text=pdf_text)
166
+ else:
167
+ parsed_items = extract_welspun_items(pdf_lines, pdf_text=pdf_text)
 
168
 
169
+ if assigned_parser == "parser_polycab":
170
+ ws, overall_item_sr, excel_write_row = map_polycab_items_to_excel_dynamic(
171
+ ws, parsed_items, resolved_item_rules, 1, overall_item_sr, excel_write_row, current_inv_number, current_inv_date, pdf_text, lut_kws, paid_kws, assigned_parser
172
+ )
173
+ elif assigned_parser == "parser_vapi_welspun":
174
+ ws, overall_item_sr, excel_write_row = map_vapi_welspun_items_to_excel_dynamic(
175
+ ws, parsed_items, resolved_item_rules, 1, overall_item_sr, excel_write_row, current_inv_number, current_inv_date, pdf_text, lut_kws, paid_kws, assigned_parser
176
+ )
177
+ else:
178
+ ws, overall_item_sr, excel_write_row = map_items_to_excel_dynamic(
179
+ ws, parsed_items, resolved_item_rules, 1, overall_item_sr, excel_write_row, current_inv_number, current_inv_date, pdf_text, lut_kws, paid_kws, assigned_parser
180
+ )
181
+
182
+ output = BytesIO()
183
+ wb.save(output)
184
+
185
+ short_shipper = selected_shipper.split(" ")[0].lower()
186
+ clean_inv = re.sub(r'[\\/*?:"<>|]', "", first_inv_no)
187
+ final_filename = f"{clean_inv}_{short_shipper}_MultiDoc.xlsx"
188
+
189
+ return output.getvalue(), final_filename
190
+ except Exception as e:
191
+ return None, str(e)