Files
ach_ui_dbtl_file_based/processors/data_mapper.py
2026-02-03 16:38:17 +05:30

150 lines
4.4 KiB
Python

#!/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) -> str:
"""
Convert ACH date string to DDMMYYYY format.
ACH format: DD/MM/YY (e.g., '19/01/26')
Output format: DDMMYYYY (e.g., '19012026')
Args:
date_str: Date string in DD/MM/YY format
Returns:
Date string in DDMMYYYY format
"""
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 in DDMMYYYY format
return parsed_date.strftime('%d%m%Y')
except Exception as e:
logger.error(f"Error converting date '{date_str}': {e}")
# Return today's date in DDMMYYYY format as fallback
return datetime.now().strftime('%d%m%Y')
@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