147 lines
4.6 KiB
Python
147 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
ACH file processing scheduler.
|
|
Runs polling loop every 30 minutes to process new files.
|
|
"""
|
|
|
|
import signal
|
|
import time
|
|
import sys
|
|
from logging_config import get_logger, setup_logging
|
|
from config import get_config
|
|
from db import OracleConnector, Repository
|
|
from sftp import SFTPClient, FileMonitor
|
|
from processors import FileProcessor
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class Scheduler:
|
|
"""Main scheduler for ACH file processing."""
|
|
|
|
def __init__(self):
|
|
"""Initialize scheduler."""
|
|
self.config = get_config()
|
|
self.config.validate()
|
|
self.running = True
|
|
self.cycle_count = 0
|
|
|
|
# Setup signal handlers for graceful shutdown
|
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
signal.signal(signal.SIGINT, self._signal_handler)
|
|
|
|
def _signal_handler(self, signum, frame):
|
|
"""Handle shutdown signals gracefully."""
|
|
logger.info(f"Received signal {signum}, shutting down gracefully...")
|
|
self.running = False
|
|
|
|
def initialize_database(self):
|
|
"""Initialize database connection and verify tables exist."""
|
|
try:
|
|
connector = OracleConnector()
|
|
if connector.test_connection():
|
|
logger.info("Database connection test passed")
|
|
repository = Repository()
|
|
repository.verify_tables_exist()
|
|
return True
|
|
else:
|
|
logger.error("Database connection test failed")
|
|
return False
|
|
except SystemExit as e:
|
|
logger.error(f"Database initialization failed: {e}")
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"Error initializing database: {e}", exc_info=True)
|
|
return False
|
|
|
|
def run_processing_cycle(self):
|
|
"""Run single file processing cycle."""
|
|
self.cycle_count += 1
|
|
logger.info(f"=== Starting processing cycle {self.cycle_count} ===")
|
|
|
|
sftp_client = SFTPClient()
|
|
repository = Repository()
|
|
|
|
try:
|
|
# Connect to SFTP
|
|
if not sftp_client.connect():
|
|
logger.error("Failed to connect to SFTP server")
|
|
return
|
|
|
|
# Get list of already processed files
|
|
processed_files = set()
|
|
for bank_code in self.config.bank_codes:
|
|
bank_processed = repository.get_processed_files(bank_code)
|
|
processed_files.update(bank_processed)
|
|
|
|
# Scan for new files
|
|
monitor = FileMonitor(sftp_client)
|
|
new_files = monitor.scan_for_new_files(list(processed_files))
|
|
|
|
if not new_files:
|
|
logger.info("No new files to process")
|
|
return
|
|
|
|
# Process files
|
|
processor = FileProcessor(repository, sftp_client)
|
|
stats = processor.process_files(new_files)
|
|
|
|
# Log summary
|
|
logger.info(f"Cycle {self.cycle_count} complete:")
|
|
logger.info(f" Total files: {stats['total']}")
|
|
logger.info(f" Successful: {stats['successful']}")
|
|
logger.info(f" Failed: {stats['failed']}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in processing cycle: {e}", exc_info=True)
|
|
|
|
finally:
|
|
sftp_client.disconnect()
|
|
|
|
def run(self):
|
|
"""Run scheduler main loop."""
|
|
logger.info("="*80)
|
|
logger.info("ACH File Processing Scheduler Started")
|
|
logger.info(f"Poll Interval: {self.config.poll_interval_minutes} minutes")
|
|
logger.info(f"Bank Codes: {', '.join(self.config.bank_codes)}")
|
|
logger.info("="*80)
|
|
|
|
# Initialize database
|
|
try:
|
|
if not self.initialize_database():
|
|
logger.error("Failed to initialize database. Exiting.")
|
|
return
|
|
except SystemExit as e:
|
|
logger.error(f"Fatal error: {e}")
|
|
raise
|
|
|
|
# Run processing loop
|
|
poll_interval_seconds = self.config.poll_interval_minutes * 60
|
|
|
|
while self.running:
|
|
try:
|
|
self.run_processing_cycle()
|
|
except Exception as e:
|
|
logger.error(f"Unexpected error in processing cycle: {e}", exc_info=True)
|
|
|
|
# Wait for next cycle
|
|
if self.running:
|
|
logger.info(f"Waiting {self.config.poll_interval_minutes} minutes until next cycle...")
|
|
time.sleep(poll_interval_seconds)
|
|
|
|
logger.info("Scheduler shutdown complete")
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
# Setup logging
|
|
setup_logging()
|
|
|
|
# Create and run scheduler
|
|
scheduler = Scheduler()
|
|
scheduler.run()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|