Files
smom-dbis-138/services/financial-tokenization/parsers/swift_fin_parser.py
defiQUG 1fb7266469 Add Oracle Aggregator and CCIP Integration
- Introduced Aggregator.sol for Chainlink-compatible oracle functionality, including round-based updates and access control.
- Added OracleWithCCIP.sol to extend Aggregator with CCIP cross-chain messaging capabilities.
- Created .gitmodules to include OpenZeppelin contracts as a submodule.
- Developed a comprehensive deployment guide in NEXT_STEPS_COMPLETE_GUIDE.md for Phase 2 and smart contract deployment.
- Implemented Vite configuration for the orchestration portal, supporting both Vue and React frameworks.
- Added server-side logic for the Multi-Cloud Orchestration Portal, including API endpoints for environment management and monitoring.
- Created scripts for resource import and usage validation across non-US regions.
- Added tests for CCIP error handling and integration to ensure robust functionality.
- Included various new files and directories for the orchestration portal and deployment scripts.
2025-12-12 14:57:48 -08:00

244 lines
10 KiB
Python

#!/usr/bin/env python3
"""
SWIFT FIN Parser
Parses SWIFT FIN messages (MT103, MT202, MT940, etc.)
"""
import re
from typing import Dict, Any, List, Optional
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
class SWIFTFINParser:
"""Parser for SWIFT FIN messages"""
SUPPORTED_MESSAGE_TYPES = [
'MT103', # Single Customer Credit Transfer
'MT202', # General Financial Institution Transfer
'MT940', # Customer Statement Message
'MT942', # Interim Transaction Report
'MT950', # Statement Message
]
def parse(self, swift_message: str) -> Dict[str, Any]:
"""Parse SWIFT FIN message"""
lines = swift_message.strip().split('\n')
message = {
'type': 'SWIFT_FIN',
'basicHeader': self._parse_block(lines, 1),
'applicationHeader': self._parse_block(lines, 2),
'userHeader': self._parse_block(lines, 3),
'text': self._parse_text_block(lines),
'trailer': self._parse_block(lines, 5),
}
# Parse message type
app_header = message['applicationHeader'].get('raw', '')
message_type = self._extract_message_type(app_header)
message['messageType'] = message_type
# Parse based on message type
if message_type == 'MT103':
message['parsed'] = self._parse_mt103(message['text'])
elif message_type == 'MT202':
message['parsed'] = self._parse_mt202(message['text'])
elif message_type == 'MT940':
message['parsed'] = self._parse_mt940(message['text'])
elif message_type == 'MT942':
message['parsed'] = self._parse_mt942(message['text'])
elif message_type == 'MT950':
message['parsed'] = self._parse_mt950(message['text'])
return message
def _parse_block(self, lines: List[str], block_num: int) -> Dict[str, Any]:
"""Parse SWIFT block"""
block_start = f"{block_num}:"
block_end = f"-{block_num}"
block_lines = []
in_block = False
for line in lines:
if line.startswith(block_start):
in_block = True
block_lines.append(line[len(block_start):])
continue
if line.startswith(block_end):
break
if in_block:
block_lines.append(line)
return {'raw': '\n'.join(block_lines)}
def _parse_text_block(self, lines: List[str]) -> str:
"""Parse SWIFT text block (block 4)"""
text_start = "4:"
text_end = "-"
text_lines = []
in_text = False
for line in lines:
if line.startswith(text_start):
in_text = True
text_lines.append(line[2:])
continue
if line.startswith(text_end) and not line.startswith("-4"):
break
if in_text:
text_lines.append(line)
return '\n'.join(text_lines)
def _extract_message_type(self, app_header: str) -> str:
"""Extract message type from application header"""
# MT103, MT202, etc. are in the application header
match = re.search(r'MT\d{3}', app_header)
return match.group(0) if match else 'UNKNOWN'
def _parse_mt103(self, text: str) -> Dict[str, Any]:
"""Parse MT103 (Single Customer Credit Transfer)"""
fields = self._parse_swift_fields(text)
return {
'senderReference': fields.get('20', ''),
'bankOperationCode': fields.get('23B', ''),
'valueDate': fields.get('32A', '').split()[0] if fields.get('32A') else '',
'currency': fields.get('32A', '').split()[1] if fields.get('32A') and len(fields.get('32A', '').split()) > 1 else '',
'amount': fields.get('32A', '').split()[2] if fields.get('32A') and len(fields.get('32A', '').split()) > 2 else '',
'orderingCustomer': fields.get('50A', '') or fields.get('50K', ''),
'sendingInstitution': fields.get('52A', '') or fields.get('52D', ''),
'orderingInstitution': fields.get('56A', '') or fields.get('56C', '') or fields.get('56D', ''),
'beneficiaryCustomer': fields.get('59', '') or fields.get('59A', ''),
'remittanceInfo': fields.get('70', ''),
'senderToReceiverInfo': fields.get('72', ''),
}
def _parse_mt202(self, text: str) -> Dict[str, Any]:
"""Parse MT202 (General Financial Institution Transfer)"""
fields = self._parse_swift_fields(text)
return {
'senderReference': fields.get('20', ''),
'transactionReference': fields.get('21', ''),
'valueDate': fields.get('32A', '').split()[0] if fields.get('32A') else '',
'currency': fields.get('32A', '').split()[1] if fields.get('32A') and len(fields.get('32A', '').split()) > 1 else '',
'amount': fields.get('32A', '').split()[2] if fields.get('32A') and len(fields.get('32A', '').split()) > 2 else '',
'sendingInstitution': fields.get('52A', '') or fields.get('52D', ''),
'orderingInstitution': fields.get('56A', '') or fields.get('56C', '') or fields.get('56D', ''),
'intermediary': fields.get('56A', '') or fields.get('56C', '') or fields.get('56D', ''),
'accountWithInstitution': fields.get('57A', '') or fields.get('57C', '') or fields.get('57D', ''),
'beneficiaryInstitution': fields.get('58A', '') or fields.get('58D', ''),
'remittanceInfo': fields.get('70', ''),
'senderToReceiverInfo': fields.get('72', ''),
}
def _parse_mt940(self, text: str) -> Dict[str, Any]:
"""Parse MT940 (Customer Statement Message)"""
fields = self._parse_swift_fields(text)
return {
'statementNumber': fields.get('28C', ''),
'accountIdentification': fields.get('25', ''),
'statementNumberSequence': fields.get('28C', '').split('/')[0] if fields.get('28C') else '',
'statementNumberPage': fields.get('28C', '').split('/')[1] if fields.get('28C') and '/' in fields.get('28C', '') else '',
'openingBalance': self._parse_balance(fields.get('60F', '') or fields.get('60M', '')),
'closingBalance': self._parse_balance(fields.get('62F', '') or fields.get('62M', '')),
'transactions': self._parse_transactions(text),
}
def _parse_mt942(self, text: str) -> Dict[str, Any]:
"""Parse MT942 (Interim Transaction Report)"""
fields = self._parse_swift_fields(text)
return {
'statementNumber': fields.get('28C', ''),
'accountIdentification': fields.get('25', ''),
'openingBalance': self._parse_balance(fields.get('60F', '') or fields.get('60M', '')),
'transactions': self._parse_transactions(text),
}
def _parse_mt950(self, text: str) -> Dict[str, Any]:
"""Parse MT950 (Statement Message)"""
fields = self._parse_swift_fields(text)
return {
'statementNumber': fields.get('28C', ''),
'accountIdentification': fields.get('25', ''),
'openingBalance': self._parse_balance(fields.get('60F', '')),
'closingBalance': self._parse_balance(fields.get('62F', '')),
'transactions': self._parse_transactions(text),
}
def _parse_swift_fields(self, text: str) -> Dict[str, str]:
"""Parse SWIFT fields from text block"""
fields = {}
current_field = None
current_value = []
for line in text.split('\n'):
# Field starts with colon (e.g., ":20:")
if re.match(r'^:\d{2}[A-Z]?:', line):
if current_field:
fields[current_field] = '\n'.join(current_value).strip()
match = re.match(r'^:(\d{2}[A-Z]?):', line)
current_field = match.group(1) if match else None
current_value = [line.split(':', 2)[2] if ':' in line[3:] else '']
elif current_field:
current_value.append(line)
if current_field:
fields[current_field] = '\n'.join(current_value).strip()
return fields
def _parse_balance(self, balance_str: str) -> Dict[str, Any]:
"""Parse balance string (e.g., "D230101USD1000,00")"""
if not balance_str:
return {}
match = re.match(r'^([DC])(\d{6})([A-Z]{3})([\d,]+)$', balance_str)
if match:
return {
'creditDebitIndicator': 'Credit' if match.group(1) == 'C' else 'Debit',
'date': match.group(2),
'currency': match.group(3),
'amount': match.group(4).replace(',', '.'),
}
return {'raw': balance_str}
def _parse_transactions(self, text: str) -> List[Dict[str, Any]]:
"""Parse transaction entries from text"""
transactions = []
fields = self._parse_swift_fields(text)
# MT940/942/950 transactions are in field 61 and 86
transaction_text = fields.get('61', '')
if transaction_text:
for line in transaction_text.split('\n'):
if line.strip():
transactions.append(self._parse_transaction_line(line))
return transactions
def _parse_transaction_line(self, line: str) -> Dict[str, Any]:
"""Parse a single transaction line (field 61)"""
# Format: ValueDate EntryDate D/C Mark Currency Amount TransactionType Reference
match = re.match(r'^(\d{6})(\d{4})?([DC])([A-Z])([N]?)(\d{1,2})([A-Z]{3})([\d,]+)(.*)$', line)
if match:
return {
'valueDate': match.group(1),
'entryDate': match.group(2),
'creditDebitIndicator': 'Credit' if match.group(3) == 'C' else 'Debit',
'fundsCode': match.group(4),
'supplementaryDetails': match.group(5),
'transactionType': match.group(6),
'currency': match.group(7),
'amount': match.group(8).replace(',', '.'),
'reference': match.group(9).strip(),
}
return {'raw': line}