mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-21 17:29:22 +00:00
Introduces a DataProcessor layer to separate transformation logic from orchestration and persistence concerns: - Created data_processors/ directory with AccountEnricher, BalanceTransformer, AnalyticsProcessor, and moved TransactionProcessor - Refactored SyncService to pure orchestrator, removing account/balance enrichment logic - Refactored DatabaseService to pure CRUD, removing analytics and transformation logic - Extracted 90+ lines of analytics SQL from DatabaseService to AnalyticsProcessor - Extracted 80+ lines of balance transformation logic to BalanceTransformer - Maintained backward compatibility - all 109 tests pass - No API contract changes This improves code clarity, testability, and maintainability while maintaining the existing API surface. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
91 lines
3.3 KiB
Python
91 lines
3.3 KiB
Python
from datetime import datetime
|
|
from typing import Any, Dict, List
|
|
|
|
|
|
class TransactionProcessor:
|
|
"""Handles processing and transformation of raw transaction data"""
|
|
|
|
def process_transactions(
|
|
self,
|
|
account_id: str,
|
|
account_info: Dict[str, Any],
|
|
transaction_data: Dict[str, Any],
|
|
) -> List[Dict[str, Any]]:
|
|
"""Process raw transaction data into standardized format"""
|
|
transactions = []
|
|
|
|
# Process booked transactions
|
|
for transaction in transaction_data.get("transactions", {}).get("booked", []):
|
|
processed = self._process_single_transaction(
|
|
account_id, account_info, transaction, "booked"
|
|
)
|
|
transactions.append(processed)
|
|
|
|
# Process pending transactions
|
|
for transaction in transaction_data.get("transactions", {}).get("pending", []):
|
|
processed = self._process_single_transaction(
|
|
account_id, account_info, transaction, "pending"
|
|
)
|
|
transactions.append(processed)
|
|
|
|
return transactions
|
|
|
|
def _process_single_transaction(
|
|
self,
|
|
account_id: str,
|
|
account_info: Dict[str, Any],
|
|
transaction: Dict[str, Any],
|
|
status: str,
|
|
) -> Dict[str, Any]:
|
|
"""Process a single transaction into standardized format"""
|
|
# Extract dates
|
|
booked_date = transaction.get("bookingDateTime") or transaction.get(
|
|
"bookingDate"
|
|
)
|
|
value_date = transaction.get("valueDateTime") or transaction.get("valueDate")
|
|
|
|
if booked_date and value_date:
|
|
min_date = min(
|
|
datetime.fromisoformat(booked_date), datetime.fromisoformat(value_date)
|
|
)
|
|
else:
|
|
date_str = booked_date or value_date
|
|
if not date_str:
|
|
raise ValueError("No valid date found in transaction")
|
|
min_date = datetime.fromisoformat(date_str)
|
|
|
|
# Extract amount and currency
|
|
transaction_amount = transaction.get("transactionAmount", {})
|
|
amount = float(transaction_amount.get("amount", 0))
|
|
currency = transaction_amount.get("currency", "")
|
|
|
|
# Extract description
|
|
description = transaction.get(
|
|
"remittanceInformationUnstructured",
|
|
",".join(transaction.get("remittanceInformationUnstructuredArray", [])),
|
|
)
|
|
|
|
# Extract transaction IDs - transactionId is now primary, internalTransactionId is reference
|
|
transaction_id = transaction.get("transactionId")
|
|
internal_transaction_id = transaction.get("internalTransactionId")
|
|
|
|
if not transaction_id:
|
|
if internal_transaction_id:
|
|
transaction_id = internal_transaction_id
|
|
else:
|
|
raise ValueError("Transaction missing required transactionId field")
|
|
|
|
return {
|
|
"accountId": account_id,
|
|
"transactionId": transaction_id,
|
|
"internalTransactionId": internal_transaction_id,
|
|
"institutionId": account_info["institution_id"],
|
|
"iban": account_info.get("iban", "N/A"),
|
|
"transactionDate": min_date,
|
|
"description": description,
|
|
"transactionValue": amount,
|
|
"transactionCurrency": currency,
|
|
"transactionStatus": status,
|
|
"rawTransaction": transaction,
|
|
}
|