Spaces:
Configuration error
Configuration error
Commit ·
f876056
1
Parent(s): e8a7b1a
quick
Browse files
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
|
| 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 |
-
#
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
| 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 |
-
|
| 32 |
if file1_obj is None or file2_obj is None:
|
| 33 |
-
|
| 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 |
-
|
| 41 |
-
logger.info(f"Input file 2 path: {file2_path}")
|
| 42 |
|
| 43 |
-
# Define the output filename
|
| 44 |
output_filename = "generated_report.xlsx"
|
| 45 |
-
|
| 46 |
|
| 47 |
# Call the core processing logic from main.py
|
| 48 |
-
|
| 49 |
result_path = process_files(file1_path, file2_path, output_filename)
|
| 50 |
-
|
| 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 |
-
|
| 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 |
-
|
| 64 |
-
|
|
|
|
| 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 |
-
#
|
| 9 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 27 |
try:
|
| 28 |
# 读取第一个文件
|
| 29 |
-
|
| 30 |
header_df = pd.read_excel(file1_path, sheet_name='HEADER')
|
| 31 |
-
|
| 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 |
-
|
| 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 |
-
|
| 41 |
wb = load_workbook(file2_path)
|
| 42 |
|
| 43 |
# Check if 'WACKER' sheet exists
|
| 44 |
if 'WACKER' not in wb.sheetnames:
|
| 45 |
-
|
| 46 |
return None # Indicate error
|
| 47 |
wacker_sheet = wb['WACKER']
|
| 48 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 76 |
continue
|
| 77 |
-
|
| 78 |
-
|
| 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 |
-
|
| 90 |
-
|
|
|
|
| 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 |
-
|
| 130 |
# Optionally fill with a default value or leave blank
|
| 131 |
# new_sheet[f'D{target_row}'] = "N/A"
|
| 132 |
except KeyError:
|
| 133 |
-
|
| 134 |
-
except Exception as
|
| 135 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 151 |
# Optionally fill with a default value or leave blank
|
| 152 |
# new_sheet[f'D{target_row}'] = "N/A"
|
| 153 |
except KeyError:
|
| 154 |
-
|
| 155 |
-
except Exception as
|
| 156 |
-
|
|
|
|
| 157 |
|
| 158 |
# 保持"批准人:"文本,并在其后添加名字
|
| 159 |
new_sheet['D29'] = f"批准人:{quality_assured_by}"
|
| 160 |
-
|
| 161 |
|
|
|
|
| 162 |
# Remove the original template sheet if it exists and wasn't intended to be kept
|
| 163 |
if 'WACKER' in wb.sheetnames:
|
| 164 |
-
|
| 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 |
-
|
| 179 |
wb.save(temp_output_path)
|
| 180 |
-
|
| 181 |
return temp_output_path # Return the full path to the temporary file
|
| 182 |
except Exception as save_error:
|
| 183 |
-
|
|
|
|
| 184 |
return None
|
| 185 |
|
| 186 |
-
except FileNotFoundError:
|
| 187 |
-
|
| 188 |
return None
|
| 189 |
-
except KeyError as
|
| 190 |
-
|
| 191 |
return None
|
| 192 |
-
except Exception as
|
| 193 |
-
# Log other unexpected errors
|
| 194 |
-
|
| 195 |
return None
|
| 196 |
|
| 197 |
|
| 198 |
# Keep the original script behavior if run directly (optional)
|
| 199 |
if __name__ == "__main__":
|
| 200 |
-
#
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 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 |
-
|
| 212 |
-
#
|
| 213 |
-
|
| 214 |
-
output_path = process_files(default_file1, default_file2, default_output)
|
| 215 |
|
| 216 |
if output_path:
|
| 217 |
-
|
| 218 |
-
logger.info(f"Direct run: Report generated successfully: {output_path}")
|
| 219 |
else:
|
| 220 |
-
|
| 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.")
|
|
|