ChatCausalGPT commited on
Commit
f876056
·
1 Parent(s): e8a7b1a
Files changed (2) hide show
  1. app.py +27 -21
  2. main.py +60 -55
app.py CHANGED
@@ -1,19 +1,20 @@
1
  # app.py
2
  import gradio as gr
3
  import logging # Import logging
4
- import sys # Import sys to output to stderr
5
  # Removed imports: pandas, openpyxl, datetime, timedelta, os, tempfile
6
 
7
  # Import the processing function from main.py
8
  from main import process_files
9
 
10
- # --- Configure Logging --- (Configure before functions)
11
- log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
12
- logging.basicConfig(level=logging.INFO, # Use INFO for general info, DEBUG for more detail
13
- format=log_format,
14
- handlers=[logging.StreamHandler(sys.stderr)]) # Output to stderr
15
-
16
- logger = logging.getLogger(__name__) # Get logger for this module
 
17
 
18
  def generate_report(file1_obj, file2_obj):
19
  """
@@ -28,40 +29,40 @@ def generate_report(file1_obj, file2_obj):
28
  Raises:
29
  gr.Error: If file processing fails.
30
  """
31
- logger.info("generate_report function called.")
32
  if file1_obj is None or file2_obj is None:
33
- logger.error("Missing one or both input files.")
34
  raise gr.Error("Please upload both required files.")
35
 
36
  try:
37
  # Gradio provides temporary paths for uploaded files
38
  file1_path = file1_obj.name
39
  file2_path = file2_obj.name
40
- logger.info(f"Input file 1 path: {file1_path}")
41
- logger.info(f"Input file 2 path: {file2_path}")
42
 
43
- # Define the output filename
44
  output_filename = "generated_report.xlsx"
45
- logger.info(f"Target output base filename: {output_filename}")
46
 
47
  # Call the core processing logic from main.py
48
- logger.info(f"Calling process_files...")
49
  result_path = process_files(file1_path, file2_path, output_filename)
50
- logger.info(f"process_files returned: {result_path}")
51
 
52
  if result_path:
53
- logger.info(f"Report generation successful. Returning path: {result_path}")
54
  # Return the path of the generated file for Gradio to serve
 
55
  return result_path
56
  else:
57
- # If process_files returned None, it means an error occurred
58
- logger.error("process_files returned None, indicating failure.")
59
  raise gr.Error("Failed to generate the report. Check application logs for details.")
60
 
61
  except Exception as e:
62
  # Catch any other unexpected errors during the wrapper execution
63
- logger.exception("An unexpected error occurred in the Gradio wrapper (generate_report)") # Logs exception info automatically
64
- raise gr.Error(f"An unexpected error occurred. Check application logs for details.") # User-friendly message
 
65
 
66
 
67
  # Create Gradio Interface
@@ -87,5 +88,10 @@ demo = gr.Interface(
87
  )
88
 
89
  if __name__ == "__main__":
 
 
 
 
 
90
  # Launch the Gradio app
91
  demo.launch(server_name="0.0.0.0") # Port defaults to 7860
 
1
  # app.py
2
  import gradio as gr
3
  import logging # Import logging
4
+ import sys # Import sys
5
  # Removed imports: pandas, openpyxl, datetime, timedelta, os, tempfile
6
 
7
  # Import the processing function from main.py
8
  from main import process_files
9
 
10
+ # Configure logging (consistent with main.py)
11
+ # Note: If main.py is imported, its basicConfig might already run.
12
+ # Re-configuring here ensures app.py specific logs also follow the format.
13
+ # Using getLogger and adding handler might be more robust in complex scenarios,
14
+ # but basicConfig is often sufficient for simpler apps.
15
+ logging.basicConfig(level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
17
+ stream=sys.stderr)
18
 
19
  def generate_report(file1_obj, file2_obj):
20
  """
 
29
  Raises:
30
  gr.Error: If file processing fails.
31
  """
32
+ logging.info("generate_report function called by Gradio.")
33
  if file1_obj is None or file2_obj is None:
34
+ logging.error("One or both files were not provided.")
35
  raise gr.Error("Please upload both required files.")
36
 
37
  try:
38
  # Gradio provides temporary paths for uploaded files
39
  file1_path = file1_obj.name
40
  file2_path = file2_obj.name
41
+ logging.info(f"Input file paths received: file1='{file1_path}', file2='{file2_path}'")
 
42
 
43
+ # Define the output filename (can be customized if needed)
44
  output_filename = "generated_report.xlsx"
45
+ logging.info(f"Target output base filename: {output_filename}")
46
 
47
  # Call the core processing logic from main.py
48
+ logging.info(f"Calling main.process_files...")
49
  result_path = process_files(file1_path, file2_path, output_filename)
50
+ logging.info(f"main.process_files returned: '{result_path}'")
51
 
52
  if result_path:
 
53
  # Return the path of the generated file for Gradio to serve
54
+ logging.info(f"Report generation successful. Returning path: {result_path}")
55
  return result_path
56
  else:
57
+ # If process_files returned None, it means an error occurred (already logged in main.py)
58
+ logging.error("main.process_files indicated failure (returned None).")
59
  raise gr.Error("Failed to generate the report. Check application logs for details.")
60
 
61
  except Exception as e:
62
  # Catch any other unexpected errors during the wrapper execution
63
+ # Log the exception with traceback before raising Gradio error
64
+ logging.exception(f"An unexpected error occurred in the Gradio wrapper (generate_report): {e}")
65
+ raise gr.Error(f"An unexpected error occurred. Check application logs.")
66
 
67
 
68
  # Create Gradio Interface
 
88
  )
89
 
90
  if __name__ == "__main__":
91
+ # Setup logging for direct script execution as well
92
+ logging.basicConfig(level=logging.INFO,
93
+ format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
94
+ stream=sys.stderr)
95
+ logging.info("Starting Gradio application...")
96
  # Launch the Gradio app
97
  demo.launch(server_name="0.0.0.0") # Port defaults to 7860
main.py CHANGED
@@ -4,9 +4,12 @@ from datetime import datetime, timedelta
4
  import os # Added for path manipulation
5
  import tempfile # Import tempfile
6
  import logging # Import logging
 
7
 
8
- # Get logger for this module
9
- logger = logging.getLogger(__name__)
 
 
10
 
11
  def process_files(file1_path, file2_path, output_filename="generated_report.xlsx"):
12
  """
@@ -23,34 +26,33 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
23
  str: The full path to the generated output Excel file in a temporary directory.
24
  Returns None if an error occurs during processing.
25
  """
26
- logger.info(f"Starting report generation with input files: {file1_path}, {file2_path}")
27
  try:
28
  # 读取第一个文件
29
- logger.info(f"Reading header from {file1_path}")
30
  header_df = pd.read_excel(file1_path, sheet_name='HEADER')
31
- logger.info(f"Reading dimension data from {file1_path}")
32
  dimension_df = pd.read_excel(file1_path, sheet_name='Dimension', skiprows=12)
33
  dimension_df.columns = dimension_df.iloc[0]
34
  dimension_df = dimension_df.iloc[1:].reset_index(drop=True)
35
- logger.info(f"Reading sand data from {file1_path}")
36
  sand_df = pd.read_excel(file1_path, sheet_name='Sand', header=None)
37
- logger.info(f"Finished reading data from {file1_path}")
38
 
39
  # 读取第二个文件
40
- logger.info(f"Loading template workbook from {file2_path}")
41
  wb = load_workbook(file2_path)
42
 
43
  # Check if 'WACKER' sheet exists
44
  if 'WACKER' not in wb.sheetnames:
45
- logger.error(f"Template file '{file2_path}' must contain a sheet named 'WACKER'.")
46
  return None # Indicate error
47
  wacker_sheet = wb['WACKER']
48
- logger.info("Template workbook loaded successfully.")
49
 
50
  # 获取Sales Order Quantity和Quality Assured By
51
  sales_order_quantity = header_df.iloc[5, 2]
52
  quality_assured_by = header_df.iloc[3, 7]
53
- logger.info(f"Retrieved Sales Order Qty: {sales_order_quantity}, Quality Assured By: {quality_assured_by}")
54
 
55
  # 定义元素和行号的对应关系 (Copied from original script)
56
  element_row_mapping = {
@@ -62,7 +64,7 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
62
  'Mg': 10, 'Mn': 11, 'Na': 12, 'Ti': 13, 'Zr': 14
63
  }
64
 
65
- logger.info(f"Processing {len(dimension_df)} entries from dimension data.")
66
  # 遍历Dimension表格中的每个Customer ID
67
  for index, row in dimension_df.iterrows():
68
  customer_id = row['Customer ID']
@@ -72,10 +74,10 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
72
 
73
  # Handle potential NaN or empty Customer ID
74
  if pd.isna(customer_id) or not str(customer_id).strip():
75
- logger.warning(f"Skipping row {index+14} due to missing or invalid Customer ID.")
76
  continue
77
-
78
- logger.debug(f"Processing Customer ID: {customer_id} (Index: {index})") # Use debug for per-item processing
79
 
80
  inspection_date_str = ""
81
  inspection_date = None # Initialize inspection_date
@@ -86,10 +88,12 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
86
  else:
87
  inspection_date = pd.to_datetime(row['Inspection Date'])
88
  inspection_date_str = inspection_date.strftime('%Y-%m-%d')
89
- except Exception as e:
90
- logger.warning(f"Could not parse Inspection Date for Customer ID {customer_id}: {e}. Skipping date fields.")
 
91
  # inspection_date remains None
92
 
 
93
  new_sheet_title = safe_customer_id
94
  # Avoid duplicate sheet names if safe_customer_id becomes the same for different original IDs
95
  sheet_count = 1
@@ -98,7 +102,8 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
98
  max_len = 31 - len(suffix)
99
  new_sheet_title = safe_customer_id[:max_len] + suffix
100
  sheet_count += 1
101
-
 
102
  new_sheet = wb.create_sheet(title=new_sheet_title)
103
 
104
  # 复制WACKER表格的内容到新工作表
@@ -106,6 +111,7 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
106
  new_sheet.append(row_wacker)
107
 
108
  # 填充数据
 
109
  new_sheet['B3'] = str(sales_order_quantity) + ' PCS'
110
  new_sheet['B4'] = customer_id # Use original ID here
111
  if inspection_date: # Only fill dates if parsing was successful
@@ -113,6 +119,7 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
113
  new_sheet['B5'] = inspection_date_str
114
  new_sheet['D5'] = (inspection_date + timedelta(days=730)).strftime('%Y-%m-%d')
115
 
 
116
  # 从sand表中获取当前customer_id的数据
117
  sand_rows = sand_df[sand_df[2] == customer_id] # 使用第3列(索引2)作为Crucible ID
118
  if not sand_rows.empty:
@@ -126,13 +133,15 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
126
  if value is not None and not pd.isna(value):
127
  new_sheet[f'D{target_row}'] = value
128
  else:
129
- logger.warning(f"Missing or invalid sand data for {element}, Customer ID {customer_id}, Col Index {source_col}")
130
  # Optionally fill with a default value or leave blank
131
  # new_sheet[f'D{target_row}'] = "N/A"
132
  except KeyError:
133
- logger.warning(f"Column index {source_col} not found in sand_row for {element}, Customer ID {customer_id}")
134
- except Exception as e:
135
- logger.error(f"Error filling element {element} for Customer ID {customer_id}: {e}")
 
 
136
 
137
  # 填充Analysis result/分析结果 (with added error handling)
138
  dim_mapping = {
@@ -147,21 +156,23 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
147
  if value is not None and not pd.isna(value):
148
  new_sheet[f'D{target_row}'] = value
149
  else:
150
- logger.warning(f"Missing or invalid dimension data for {source_col_name}, Customer ID {customer_id}")
151
  # Optionally fill with a default value or leave blank
152
  # new_sheet[f'D{target_row}'] = "N/A"
153
  except KeyError:
154
- logger.warning(f"Column '{source_col_name}' not found in dimension_df for Customer ID {customer_id}")
155
- except Exception as e:
156
- logger.error(f"Error filling dimension {source_col_name} for Customer ID {customer_id}: {e}")
 
157
 
158
  # 保持"批准人:"文本,并在其后添加名字
159
  new_sheet['D29'] = f"批准人:{quality_assured_by}"
160
- logger.debug(f"Finished processing data for Customer ID: {customer_id}") # Use debug
161
 
 
162
  # Remove the original template sheet if it exists and wasn't intended to be kept
163
  if 'WACKER' in wb.sheetnames:
164
- logger.info("Removing 'WACKER' template sheet from the output workbook.")
165
  del wb['WACKER'] # Remove template if no longer needed
166
 
167
  # Create a temporary file path for the output
@@ -170,52 +181,46 @@ def process_files(file1_path, file2_path, output_filename="generated_report.xlsx
170
  # Ensure the base output filename is used, not a potentially problematic one from input args
171
  safe_output_filename = os.path.basename(output_filename if output_filename else "generated_report.xlsx")
172
  # Create a unique temporary file path
173
- # Using mkstemp gives more control, but let's try simple join first
174
- # _, temp_output_path = tempfile.mkstemp(suffix=".xlsx", prefix="report_", dir=temp_dir)
175
- # Let's use a predictable name within the temp dir, might be easier for Gradio/platform
176
  temp_output_path = os.path.join(temp_dir, safe_output_filename)
177
 
178
- logger.info(f"Attempting to save report to temporary path: {temp_output_path}")
179
  wb.save(temp_output_path)
180
- logger.info(f"Successfully saved report to: {temp_output_path}")
181
  return temp_output_path # Return the full path to the temporary file
182
  except Exception as save_error:
183
- logger.exception(f"Error saving workbook to temporary path {temp_output_path}") # Log exception
 
184
  return None
185
 
186
- except FileNotFoundError:
187
- logger.error(f"Error: Input file not found. Check paths: {file1_path}, {file2_path}")
188
  return None
189
- except KeyError as e:
190
- logger.exception(f"Error: Missing expected column or sheet name: {e}. Check input file formats.")
191
  return None
192
- except Exception as e:
193
- # Log other unexpected errors
194
- logger.exception(f"An unexpected error occurred in process_files: {e}")
195
  return None
196
 
197
 
198
  # Keep the original script behavior if run directly (optional)
199
  if __name__ == "__main__":
200
- # Configure basic logging for direct script execution if needed
201
- # Note: app.py usually handles the main config when run via Gradio
202
- if not logging.getLogger().hasHandlers(): # Only configure if not already configured by app.py import
203
- log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
204
- logging.basicConfig(level=logging.INFO, format=log_format)
205
 
206
  # Define default input/output files for direct execution
207
  default_file1 = '1.xls'
208
  default_file2 = '2.xlsx'
209
- default_output = '2_updated.xlsx'
210
 
211
- print(f"Running script directly. Processing {default_file1} and {default_file2}...")
212
- # Use logger here too if desired, but print might be fine for direct runs
213
- logger.info(f"Running script directly. Processing {default_file1} and {default_file2}...")
214
- output_path = process_files(default_file1, default_file2, default_output)
215
 
216
  if output_path:
217
- print(f"Report generated successfully: {output_path}")
218
- logger.info(f"Direct run: Report generated successfully: {output_path}")
219
  else:
220
- print("Report generation failed.")
221
- logger.error("Direct run: Report generation failed.")
 
4
  import os # Added for path manipulation
5
  import tempfile # Import tempfile
6
  import logging # Import logging
7
+ import sys # Import sys to target stderr
8
 
9
+ # Configure logging
10
+ logging.basicConfig(level=logging.INFO,
11
+ format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
12
+ stream=sys.stderr) # Log to stderr, usually captured by platforms
13
 
14
  def process_files(file1_path, file2_path, output_filename="generated_report.xlsx"):
15
  """
 
26
  str: The full path to the generated output Excel file in a temporary directory.
27
  Returns None if an error occurs during processing.
28
  """
29
+ logging.info(f"Starting report generation. Input 1: {file1_path}, Input 2: {file2_path}")
30
  try:
31
  # 读取第一个文件
32
+ logging.info(f"Reading header from: {file1_path}")
33
  header_df = pd.read_excel(file1_path, sheet_name='HEADER')
34
+ logging.info(f"Reading dimension data from: {file1_path}")
35
  dimension_df = pd.read_excel(file1_path, sheet_name='Dimension', skiprows=12)
36
  dimension_df.columns = dimension_df.iloc[0]
37
  dimension_df = dimension_df.iloc[1:].reset_index(drop=True)
38
+ logging.info(f"Reading sand data from: {file1_path}")
39
  sand_df = pd.read_excel(file1_path, sheet_name='Sand', header=None)
 
40
 
41
  # 读取第二个文件
42
+ logging.info(f"Loading template workbook: {file2_path}")
43
  wb = load_workbook(file2_path)
44
 
45
  # Check if 'WACKER' sheet exists
46
  if 'WACKER' not in wb.sheetnames:
47
+ logging.error("Error: Template file must contain a sheet named 'WACKER'.")
48
  return None # Indicate error
49
  wacker_sheet = wb['WACKER']
50
+ logging.info("Template sheet 'WACKER' found.")
51
 
52
  # 获取Sales Order Quantity和Quality Assured By
53
  sales_order_quantity = header_df.iloc[5, 2]
54
  quality_assured_by = header_df.iloc[3, 7]
55
+ logging.info(f"Extracted Sales Order Qty: {sales_order_quantity}, Assured By: {quality_assured_by}")
56
 
57
  # 定义元素和行号的对应关系 (Copied from original script)
58
  element_row_mapping = {
 
64
  'Mg': 10, 'Mn': 11, 'Na': 12, 'Ti': 13, 'Zr': 14
65
  }
66
 
67
+ logging.info("Starting iteration through dimension data.")
68
  # 遍历Dimension表格中的每个Customer ID
69
  for index, row in dimension_df.iterrows():
70
  customer_id = row['Customer ID']
 
74
 
75
  # Handle potential NaN or empty Customer ID
76
  if pd.isna(customer_id) or not str(customer_id).strip():
77
+ logging.warning(f"Skipping row {index+14} due to missing or invalid Customer ID.")
78
  continue
79
+
80
+ logging.debug(f"Processing Customer ID: {customer_id} (Sheet Name: {safe_customer_id})") # Debug level for per-row info
81
 
82
  inspection_date_str = ""
83
  inspection_date = None # Initialize inspection_date
 
88
  else:
89
  inspection_date = pd.to_datetime(row['Inspection Date'])
90
  inspection_date_str = inspection_date.strftime('%Y-%m-%d')
91
+ logging.debug(f"Parsed Inspection Date for {customer_id}: {inspection_date_str}")
92
+ except Exception as date_parse_e:
93
+ logging.warning(f"Could not parse Inspection Date for Customer ID {customer_id}: {date_parse_e}. Skipping date fields.")
94
  # inspection_date remains None
95
 
96
+
97
  new_sheet_title = safe_customer_id
98
  # Avoid duplicate sheet names if safe_customer_id becomes the same for different original IDs
99
  sheet_count = 1
 
102
  max_len = 31 - len(suffix)
103
  new_sheet_title = safe_customer_id[:max_len] + suffix
104
  sheet_count += 1
105
+
106
+ logging.debug(f"Creating new sheet with title: {new_sheet_title}")
107
  new_sheet = wb.create_sheet(title=new_sheet_title)
108
 
109
  # 复制WACKER表格的内容到新工作表
 
111
  new_sheet.append(row_wacker)
112
 
113
  # 填充数据
114
+ logging.debug(f"Populating sheet {new_sheet_title} with data for {customer_id}")
115
  new_sheet['B3'] = str(sales_order_quantity) + ' PCS'
116
  new_sheet['B4'] = customer_id # Use original ID here
117
  if inspection_date: # Only fill dates if parsing was successful
 
119
  new_sheet['B5'] = inspection_date_str
120
  new_sheet['D5'] = (inspection_date + timedelta(days=730)).strftime('%Y-%m-%d')
121
 
122
+
123
  # 从sand表中获取当前customer_id的数据
124
  sand_rows = sand_df[sand_df[2] == customer_id] # 使用第3列(索引2)作为Crucible ID
125
  if not sand_rows.empty:
 
133
  if value is not None and not pd.isna(value):
134
  new_sheet[f'D{target_row}'] = value
135
  else:
136
+ logging.warning(f"Missing or invalid sand data for {element}, Customer ID {customer_id}, Col Index {source_col}")
137
  # Optionally fill with a default value or leave blank
138
  # new_sheet[f'D{target_row}'] = "N/A"
139
  except KeyError:
140
+ logging.warning(f"Column index {source_col} not found in sand_row for {element}, Customer ID {customer_id}")
141
+ except Exception as elem_fill_e:
142
+ # Log error but continue processing other elements/rows
143
+ logging.error(f"Error filling element {element} for Customer ID {customer_id}: {elem_fill_e}")
144
+
145
 
146
  # 填充Analysis result/分析结果 (with added error handling)
147
  dim_mapping = {
 
156
  if value is not None and not pd.isna(value):
157
  new_sheet[f'D{target_row}'] = value
158
  else:
159
+ logging.warning(f"Missing or invalid dimension data for {source_col_name}, Customer ID {customer_id}")
160
  # Optionally fill with a default value or leave blank
161
  # new_sheet[f'D{target_row}'] = "N/A"
162
  except KeyError:
163
+ logging.warning(f"Column '{source_col_name}' not found in dimension_df for Customer ID {customer_id}")
164
+ except Exception as dim_fill_e:
165
+ # Log error but continue processing other dimensions/rows
166
+ logging.error(f"Error filling dimension {source_col_name} for Customer ID {customer_id}: {dim_fill_e}")
167
 
168
  # 保持"批准人:"文本,并在其后添加名字
169
  new_sheet['D29'] = f"批准人:{quality_assured_by}"
170
+ logging.debug(f"Finished populating sheet for Customer ID: {customer_id}")
171
 
172
+ logging.info("Finished iterating through dimension data.")
173
  # Remove the original template sheet if it exists and wasn't intended to be kept
174
  if 'WACKER' in wb.sheetnames:
175
+ logging.info("Removing original 'WACKER' template sheet.")
176
  del wb['WACKER'] # Remove template if no longer needed
177
 
178
  # Create a temporary file path for the output
 
181
  # Ensure the base output filename is used, not a potentially problematic one from input args
182
  safe_output_filename = os.path.basename(output_filename if output_filename else "generated_report.xlsx")
183
  # Create a unique temporary file path
 
 
 
184
  temp_output_path = os.path.join(temp_dir, safe_output_filename)
185
 
186
+ logging.info(f"Attempting to save report to temporary path: {temp_output_path}")
187
  wb.save(temp_output_path)
188
+ logging.info(f"Successfully saved report to: {temp_output_path}")
189
  return temp_output_path # Return the full path to the temporary file
190
  except Exception as save_error:
191
+ # Log the exception with traceback
192
+ logging.exception(f"Error saving workbook to temporary path {temp_output_path}: {save_error}")
193
  return None
194
 
195
+ except FileNotFoundError as fnf_error:
196
+ logging.exception(f"Error: Input file not found. Check paths: {file1_path}, {file2_path}. Error: {fnf_error}")
197
  return None
198
+ except KeyError as key_error:
199
+ logging.exception(f"Error: Missing expected column or sheet name: {key_error}. Check input file formats.")
200
  return None
201
+ except Exception as general_error:
202
+ # Log other unexpected errors with traceback
203
+ logging.exception(f"An unexpected error occurred in process_files: {general_error}")
204
  return None
205
 
206
 
207
  # Keep the original script behavior if run directly (optional)
208
  if __name__ == "__main__":
209
+ # Setup logging for direct script execution as well
210
+ logging.basicConfig(level=logging.INFO,
211
+ format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s',
212
+ stream=sys.stderr)
 
213
 
214
  # Define default input/output files for direct execution
215
  default_file1 = '1.xls'
216
  default_file2 = '2.xlsx'
217
+ default_output = '2_updated.xlsx' # For direct run, save locally
218
 
219
+ logging.info(f"Running script directly. Processing {default_file1} and {default_file2}...")
220
+ # For direct run, let's keep saving locally for simplicity, unless specified otherwise
221
+ output_path = process_files(default_file1, default_file2, default_output) # Use local path for direct run
 
222
 
223
  if output_path:
224
+ logging.info(f"Report generated successfully: {output_path}")
 
225
  else:
226
+ logging.error("Report generation failed.")