product
This commit is contained in:
147
processors/data_mapper.py
Normal file
147
processors/data_mapper.py
Normal file
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Data mapper for field transformations.
|
||||
Maps ACH parser output to database format.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Dict, Any
|
||||
from logging_config import get_logger
|
||||
from db.models import TransactionRecord
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class DataMapper:
|
||||
"""Maps parsed ACH transactions to database records."""
|
||||
|
||||
@staticmethod
|
||||
def convert_date(date_str: str) -> datetime.date:
|
||||
"""
|
||||
Convert ACH date string to Python date.
|
||||
|
||||
ACH format: DD/MM/YY (e.g., '19/01/26')
|
||||
|
||||
Args:
|
||||
date_str: Date string in DD/MM/YY format
|
||||
|
||||
Returns:
|
||||
datetime.date object
|
||||
"""
|
||||
try:
|
||||
if not date_str or len(date_str) < 8:
|
||||
raise ValueError(f"Invalid date format: {date_str}")
|
||||
|
||||
# Parse DD/MM/YY
|
||||
parsed_date = datetime.strptime(date_str, '%d/%m/%y')
|
||||
return parsed_date.date()
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting date '{date_str}': {e}")
|
||||
# Return today's date as fallback
|
||||
return datetime.now().date()
|
||||
|
||||
@staticmethod
|
||||
def calculate_txnind(amount_str: str) -> str:
|
||||
"""
|
||||
Calculate transaction indicator from amount.
|
||||
|
||||
Args:
|
||||
amount_str: Amount as string
|
||||
|
||||
Returns:
|
||||
'CR' for credit (>= 0), 'DR' for debit (< 0)
|
||||
"""
|
||||
try:
|
||||
amount = Decimal(amount_str.strip())
|
||||
return 'DR' if amount < 0 else 'CR'
|
||||
except Exception as e:
|
||||
logger.error(f"Error calculating TXNIND for amount '{amount_str}': {e}")
|
||||
return 'CR' # Default to credit
|
||||
|
||||
@staticmethod
|
||||
def convert_amount(amount_str: str) -> Decimal:
|
||||
"""
|
||||
Convert amount string to Decimal.
|
||||
|
||||
Args:
|
||||
amount_str: Amount as string
|
||||
|
||||
Returns:
|
||||
Decimal representation of amount
|
||||
"""
|
||||
try:
|
||||
if not amount_str:
|
||||
return Decimal('0')
|
||||
amount = Decimal(amount_str.strip())
|
||||
return abs(amount) # Store absolute value, use TXNIND for sign
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting amount '{amount_str}': {e}")
|
||||
return Decimal('0')
|
||||
|
||||
@classmethod
|
||||
def map_transaction(
|
||||
cls,
|
||||
parsed_transaction: Dict[str, Any],
|
||||
bankcode: str
|
||||
) -> TransactionRecord:
|
||||
"""
|
||||
Map parsed transaction to database record.
|
||||
|
||||
Args:
|
||||
parsed_transaction: Transaction from ACHParser
|
||||
bankcode: Bank code for this transaction
|
||||
|
||||
Returns:
|
||||
TransactionRecord ready for database insertion
|
||||
"""
|
||||
try:
|
||||
amount_str = parsed_transaction.get('amount', '0')
|
||||
amount = cls.convert_amount(amount_str)
|
||||
txnind = cls.calculate_txnind(amount_str)
|
||||
tran_date = cls.convert_date(parsed_transaction.get('date', ''))
|
||||
|
||||
record = TransactionRecord(
|
||||
narration=parsed_transaction.get('remarks', '')[:500], # Limit to 500 chars
|
||||
status=parsed_transaction.get('sys', ''),
|
||||
bankcode=bankcode,
|
||||
jrnl_id=parsed_transaction.get('jrnl_no', ''),
|
||||
tran_date=tran_date,
|
||||
cbs_acct=parsed_transaction.get('cust_acct', ''),
|
||||
tran_amt=amount,
|
||||
txnind=txnind,
|
||||
)
|
||||
|
||||
return record
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error mapping transaction: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
@classmethod
|
||||
def map_transactions(
|
||||
cls,
|
||||
parsed_transactions: list,
|
||||
bankcode: str
|
||||
) -> list:
|
||||
"""
|
||||
Map multiple transactions.
|
||||
|
||||
Args:
|
||||
parsed_transactions: List of transactions from ACHParser
|
||||
bankcode: Bank code for these transactions
|
||||
|
||||
Returns:
|
||||
List of TransactionRecord objects
|
||||
"""
|
||||
records = []
|
||||
for txn in parsed_transactions:
|
||||
try:
|
||||
record = cls.map_transaction(txn, bankcode)
|
||||
records.append(record)
|
||||
except Exception as e:
|
||||
logger.warning(f"Skipping transaction due to error: {e}")
|
||||
continue
|
||||
|
||||
logger.info(f"Mapped {len(records)} transactions for bank {bankcode}")
|
||||
return records
|
||||
Reference in New Issue
Block a user