| @@ -8,9 +8,13 @@ import os | |||||
| import sys | import sys | ||||
| import glob | import glob | ||||
| import re | import re | ||||
| import argparse | |||||
| import logging | |||||
| import pandas as pd | import pandas as pd | ||||
| from pathlib import Path | from pathlib import Path | ||||
| logger = logging.getLogger(__name__) | |||||
| def parse_norwegian_number(value): | def parse_norwegian_number(value): | ||||
| """Convert Norwegian number format (comma decimal) to float""" | """Convert Norwegian number format (comma decimal) to float""" | ||||
| if pd.isna(value) or value == '': | if pd.isna(value) or value == '': | ||||
| @@ -30,7 +34,7 @@ def parse_norwegian_date(date_str): | |||||
| # Parse DD.MM.YYYY and convert to date object | # Parse DD.MM.YYYY and convert to date object | ||||
| return pd.to_datetime(date_str, format='%d.%m.%Y') | return pd.to_datetime(date_str, format='%d.%m.%Y') | ||||
| except (ValueError, TypeError): | except (ValueError, TypeError): | ||||
| print(f"Invalid date format: {date_str}") | |||||
| logger.error(f"Invalid date format: {date_str}") | |||||
| exit(1) | exit(1) | ||||
| def convert_memo(original): | def convert_memo(original): | ||||
| @@ -184,7 +188,7 @@ def process_bank_statement(file_path, parse_function, delimiter, encoding): | |||||
| elif file_extension in [".xlsx", ".xls"]: | elif file_extension in [".xlsx", ".xls"]: | ||||
| data = pd.read_excel(file_path) | data = pd.read_excel(file_path) | ||||
| else: | else: | ||||
| print(f"Skipping unsupported file type: {file_path}") | |||||
| logger.warning(f"Skipping unsupported file type: {file_path}") | |||||
| return pd.DataFrame() | return pd.DataFrame() | ||||
| # Call the appropriate bank-specific parsing function | # Call the appropriate bank-specific parsing function | ||||
| @@ -192,7 +196,7 @@ def process_bank_statement(file_path, parse_function, delimiter, encoding): | |||||
| return ynab_data | return ynab_data | ||||
| except Exception as e: | except Exception as e: | ||||
| print(f"Error processing file {file_path}: {e}") | |||||
| logger.error(f"Error processing file {file_path}: {e}") | |||||
| raise e | raise e | ||||
| return pd.DataFrame() | return pd.DataFrame() | ||||
| @@ -217,26 +221,30 @@ def find_bank_config(filename): | |||||
| return None, None | return None, None | ||||
| def convert_bank_statements_to_ynab(input_files=None): | |||||
| def convert_bank_statements_to_ynab(input_files, output_directory): | |||||
| """ | """ | ||||
| Convert bank statements to YNAB format | Convert bank statements to YNAB format | ||||
| Args: | Args: | ||||
| input_files (list): Optional list of specific files to process | |||||
| If None, processes all files in current directory | |||||
| input_files (list): List of specific files to process | |||||
| If empty, processes all files in current directory | |||||
| """ | """ | ||||
| current_directory = Path.cwd() | current_directory = Path.cwd() | ||||
| output_directory = current_directory / "YNAB_Outputs" | |||||
| if not output_directory: | |||||
| output_directory = current_directory / "YNAB_Outputs" | |||||
| logger.debug(f"No output directory set. Defaulting to {output_directory}") | |||||
| # Create output directory if it doesn't exist | # Create output directory if it doesn't exist | ||||
| output_directory.mkdir(exist_ok=True) | |||||
| output_directory.mkdir(exist_ok=True, parents=True) | |||||
| # Get list of files to process | # Get list of files to process | ||||
| if input_files: | if input_files: | ||||
| print(f"Processing {len(input_files)} dragged file(s)...") | |||||
| logger.info(f"Processing {len(input_files)} dragged file(s)...") | |||||
| files_to_process = [Path(f) for f in input_files if Path(f).exists()] | files_to_process = [Path(f) for f in input_files if Path(f).exists()] | ||||
| else: | else: | ||||
| print("Processing all files in current directory...") | |||||
| logger.info("Processing all files in current directory...") | |||||
| logger.debug(f"Current directory is {current_directory}") | |||||
| files_to_process = [] | files_to_process = [] | ||||
| # Collect all files matching any bank pattern | # Collect all files matching any bank pattern | ||||
| for bank_config in BANKS.values(): | for bank_config in BANKS.values(): | ||||
| @@ -249,17 +257,17 @@ def convert_bank_statements_to_ynab(input_files=None): | |||||
| # Process each file | # Process each file | ||||
| for file_path in files_to_process: | for file_path in files_to_process: | ||||
| if not file_path.exists(): | if not file_path.exists(): | ||||
| print(f"File not found: {file_path}") | |||||
| logger.warning(f"File not found: {file_path}") | |||||
| continue | continue | ||||
| # Find matching bank configuration | # Find matching bank configuration | ||||
| bank_name, bank_config = find_bank_config(file_path.name) | bank_name, bank_config = find_bank_config(file_path.name) | ||||
| if not bank_config: | if not bank_config: | ||||
| print(f"No bank configuration found for file: {file_path.name}") | |||||
| logger.warning(f"No bank configuration found for file: {file_path.name}") | |||||
| continue | continue | ||||
| print(f"Processing file: {file_path} for {bank_name}") | |||||
| logger.info(f"Processing file: {file_path} for {bank_name}") | |||||
| parse_function = bank_config["parse_function"] | parse_function = bank_config["parse_function"] | ||||
| delimiter = bank_config.get("delimiter", ",") | delimiter = bank_config.get("delimiter", ",") | ||||
| @@ -269,7 +277,7 @@ def convert_bank_statements_to_ynab(input_files=None): | |||||
| ynab_data = process_bank_statement(str(file_path), parse_function, delimiter, encoding) | ynab_data = process_bank_statement(str(file_path), parse_function, delimiter, encoding) | ||||
| if ynab_data.empty: | if ynab_data.empty: | ||||
| print(f"No data processed for {file_path}") | |||||
| logger.warning(f"No data processed for {file_path}") | |||||
| continue | continue | ||||
| filename_placeholders = { | filename_placeholders = { | ||||
| @@ -295,22 +303,49 @@ def convert_bank_statements_to_ynab(input_files=None): | |||||
| # Export to CSV for YNAB import | # Export to CSV for YNAB import | ||||
| ynab_data.to_csv(output_file, index=False) | ynab_data.to_csv(output_file, index=False) | ||||
| print(f"Data saved to {output_file}") | |||||
| logger.info(f"Data saved to {output_file}") | |||||
| files_processed = True | files_processed = True | ||||
| if not files_processed: | if not files_processed: | ||||
| print("No files were processed. Make sure your files match the expected patterns.") | |||||
| logger.warning("No files were processed. Make sure your files match the expected patterns.") | |||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||
| # Check if files were dragged onto the script | |||||
| if len(sys.argv) > 1: | |||||
| # Files were dragged - process them | |||||
| files = sys.argv[1:] | |||||
| convert_bank_statements_to_ynab(files) | |||||
| else: | |||||
| # No files dragged - run normal directory processing | |||||
| convert_bank_statements_to_ynab() | |||||
| # Keep window open on Mac so user can see results | |||||
| input("\nPress Enter to close...") | |||||
| parser = argparse.ArgumentParser( | |||||
| prog='YNAB', | |||||
| description='Prepare bank transcripts for import to You Need A Budget', | |||||
| ) | |||||
| parser.add_argument( | |||||
| 'filenames', | |||||
| type=Path, | |||||
| nargs='*', | |||||
| help='The files to process', | |||||
| ) | |||||
| parser.add_argument( | |||||
| '-o', '--output-dir', | |||||
| type=Path, | |||||
| default=None, | |||||
| help='The location to store the converted files', | |||||
| ) | |||||
| parser.add_argument( | |||||
| '-v', '--verbose', | |||||
| default=0, | |||||
| action='count', | |||||
| help='Increase logging verbosity', | |||||
| ) | |||||
| args = parser.parse_args() | |||||
| if args.verbose <= 0: | |||||
| log_level = logging.WARNING | |||||
| elif args.verbose == 1: | |||||
| log_level = logging.INFO | |||||
| elif args.verbose >= 2: | |||||
| log_level = logging.DEBUG | |||||
| logging.basicConfig(level=log_level) | |||||
| convert_bank_statements_to_ynab(args.filenames, args.output_dir) | |||||