refactor: use Decimal instead of float for monetary calculations
- calculations.py: Use Decimal for commission percentages, exchange rates, and client balances. Added to_decimal() helper for safe float conversion. Changed from banker's rounding to ROUND_HALF_UP. - models.py: Changed all fiat amounts, percentages, and exchange rates to Decimal. Added json_encoders for API serialization. - transaction_processor.py: Convert to Decimal at data ingestion boundary (CSV parsing). Updated all defaults and calculations to use Decimal. - tests: Updated to work with Decimal return types. This prevents floating-point precision issues in financial calculations. All 23 tests pass. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
397fd4b002
commit
6e86f53962
4 changed files with 180 additions and 101 deletions
|
|
@ -10,7 +10,7 @@ from decimal import Decimal
|
|||
from typing import Dict, List, Tuple
|
||||
|
||||
# Import from the parent package (following lnurlp pattern)
|
||||
from ..calculations import calculate_commission, calculate_distribution, calculate_exchange_rate
|
||||
from ..calculations import calculate_commission, calculate_distribution, calculate_exchange_rate, to_decimal
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -245,11 +245,12 @@ class TestDistributionCalculation:
|
|||
|
||||
# Convert each client's sats back to fiat
|
||||
total_fiat_distributed = sum(
|
||||
sats / exchange_rate for sats in distributions.values()
|
||||
Decimal(sats) / exchange_rate for sats in distributions.values()
|
||||
)
|
||||
|
||||
# Should equal original fiat amount (within small rounding tolerance)
|
||||
assert abs(total_fiat_distributed - fiat_amount) < 0.01, \
|
||||
fiat_decimal = to_decimal(fiat_amount)
|
||||
assert abs(total_fiat_distributed - fiat_decimal) < Decimal("0.01"), \
|
||||
f"Fiat round-trip failed: {total_fiat_distributed:.2f} != {fiat_amount:.2f} " \
|
||||
f"(crypto={crypto_atoms}, comm={comm_pct}, discount={discount})"
|
||||
|
||||
|
|
@ -287,11 +288,12 @@ class TestEmpiricalTransactions:
|
|||
"expected_base_sats": 259029,
|
||||
"expected_commission_sats": 7771,
|
||||
"expected_distributions": {
|
||||
# 259029 / 2 = 129514.5 → both get 129514 or 129515
|
||||
# With banker's rounding: 129514.5 → 129514 (even)
|
||||
# Remainder of 1 sat goes to first client by fractional sort
|
||||
"client_a": 129515,
|
||||
"client_b": 129514,
|
||||
# 259029 / 2 = 129514.5 → both round to 129515 (ROUND_HALF_UP)
|
||||
# Total = 259030, remainder = -1
|
||||
# Both have same fractional (-0.5), client_a is first alphabetically
|
||||
# So client_a gets -1 adjustment
|
||||
"client_a": 129514,
|
||||
"client_b": 129515,
|
||||
},
|
||||
},
|
||||
# Add more scenarios from your real data!
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue