From d1242e5cd2400c19d8c79f2ce76be81c60f0c447 Mon Sep 17 00:00:00 2001 From: padreug Date: Tue, 8 Jul 2025 06:14:52 +0200 Subject: [PATCH 1/2] Refactor transaction data handling in LamassuTransactionProcessor: Improved consistency in processing None and empty values during data ingestion. Defaulted numeric fields to 0 and percentage fields to 0.0 for better error handling. Ensured clean extraction of transaction details, enhancing reliability in transaction processing. --- transaction_processor.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/transaction_processor.py b/transaction_processor.py index 22c4edd..a07be52 100644 --- a/transaction_processor.py +++ b/transaction_processor.py @@ -493,14 +493,26 @@ class LamassuTransactionProcessor: # Convert string values to appropriate types processed_row = {} for key, value in row.items(): - if value == '': - processed_row[key] = None + # Handle None/empty values consistently at data ingestion boundary + if value == '' or value is None: + if key in ['fiat_amount', 'crypto_amount']: + processed_row[key] = 0 # Default numeric fields to 0 + elif key in ['commission_percentage', 'discount']: + processed_row[key] = 0.0 # Default percentage fields to 0.0 + else: + processed_row[key] = None # Keep None for non-numeric fields elif key in ['transaction_id', 'device_id', 'crypto_code', 'fiat_code']: processed_row[key] = str(value) elif key in ['fiat_amount', 'crypto_amount']: - processed_row[key] = int(float(value)) if value else 0 + try: + processed_row[key] = int(float(value)) + except (ValueError, TypeError): + processed_row[key] = 0 # Fallback to 0 for invalid values elif key in ['commission_percentage', 'discount']: - processed_row[key] = float(value) if value else 0.0 + try: + processed_row[key] = float(value) + except (ValueError, TypeError): + processed_row[key] = 0.0 # Fallback to 0.0 for invalid values elif key == 'transaction_time': from datetime import datetime # Parse PostgreSQL timestamp format and ensure it's in UTC for consistency @@ -628,11 +640,11 @@ class LamassuTransactionProcessor: logger.info("No Flow Mode clients found - skipping distribution") return {} - # Extract transaction details with None-safe defaults - crypto_atoms = transaction.get("crypto_amount") # Total sats with commission baked in - fiat_amount = transaction.get("fiat_amount") # Actual fiat dispensed (principal only) - commission_percentage = transaction.get("commission_percentage") # Already stored as decimal (e.g., 0.045) - discount = transaction.get("discount") # Discount percentage + # Extract transaction details - guaranteed clean from data ingestion + crypto_atoms = transaction.get("crypto_amount", 0) # Total sats with commission baked in + fiat_amount = transaction.get("fiat_amount", 0) # Actual fiat dispensed (principal only) + commission_percentage = transaction.get("commission_percentage", 0.0) # Already stored as decimal (e.g., 0.045) + discount = transaction.get("discount", 0.0) # Discount percentage transaction_time = transaction.get("transaction_time") # ATM transaction timestamp for temporal accuracy # Normalize transaction_time to UTC if present @@ -995,11 +1007,11 @@ class LamassuTransactionProcessor: async def store_lamassu_transaction(self, transaction: Dict[str, Any]) -> Optional[str]: """Store the Lamassu transaction in our database for audit and UI""" try: - # Extract and validate transaction data + # Extract transaction data - guaranteed clean from data ingestion boundary crypto_atoms = transaction.get("crypto_amount", 0) fiat_amount = transaction.get("fiat_amount", 0) - commission_percentage = transaction.get("commission_percentage") or 0.0 - discount = transaction.get("discount") or 0.0 + commission_percentage = transaction.get("commission_percentage", 0.0) + discount = transaction.get("discount", 0.0) transaction_time = transaction.get("transaction_time") # Normalize transaction_time to UTC if present @@ -1145,8 +1157,8 @@ class LamassuTransactionProcessor: # Calculate commission amount for sending to commission wallet crypto_atoms = transaction.get("crypto_amount", 0) - commission_percentage = transaction.get("commission_percentage") or 0.0 - discount = transaction.get("discount") or 0.0 + commission_percentage = transaction.get("commission_percentage", 0.0) + discount = transaction.get("discount", 0.0) if commission_percentage and commission_percentage > 0: effective_commission = commission_percentage * (100 - discount) / 100 From bd7a72f3c0ad9bda558c80c0e53e5af02c75e6b5 Mon Sep 17 00:00:00 2001 From: padreug Date: Sat, 19 Jul 2025 00:21:13 +0200 Subject: [PATCH 2/2] Improve balance verification in LamassuTransactionProcessor: Added rounding to two decimal places for balance and fiat amounts to ensure precision in comparisons. Enhanced logging for insufficient balance scenarios, improving clarity in transaction processing and error reporting. --- transaction_processor.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/transaction_processor.py b/transaction_processor.py index a07be52..9a58408 100644 --- a/transaction_processor.py +++ b/transaction_processor.py @@ -849,10 +849,13 @@ class LamassuTransactionProcessor: logger.error(f"CRITICAL: Client {client_id[:8]}... has negative balance ({current_balance.remaining_balance:.2f} GTQ) - REFUSING payment of {distribution['sats_amount']} sats") continue - # Verify balance is sufficient for this distribution + # Verify balance is sufficient for this distribution (round to 2 decimal places to match DECIMAL(10,2) precision) fiat_equivalent = distribution["fiat_amount"] # Amount in GTQ - if current_balance.remaining_balance < fiat_equivalent: - logger.error(f"CRITICAL: Client {client_id[:8]}... insufficient balance ({current_balance.remaining_balance:.2f} < {fiat_equivalent:.2f} GTQ) - REFUSING payment") + # Round both values to 2 decimal places to match database precision and avoid floating point comparison issues + balance_rounded = round(current_balance.remaining_balance, 2) + amount_rounded = round(fiat_equivalent, 2) + if balance_rounded < amount_rounded: + logger.error(f"CRITICAL: Client {client_id[:8]}... insufficient balance ({balance_rounded:.2f} < {amount_rounded:.2f} GTQ) - REFUSING payment") continue logger.info(f"Client {client_id[:8]}... pre-payment balance check: {current_balance.remaining_balance:.2f} GTQ - SUFFICIENT for {fiat_equivalent:.2f} GTQ payment")