fix: add SQLite compatibility for Decimal types

SQLite doesn't support Decimal natively - it stores DECIMAL columns as
REAL (float). This caused sqlite3.ProgrammingError when writing Decimal
values.

Changes:
- Add prepare_for_db() helper to convert Decimal→float before writes
- Add Pydantic validators to convert float→Decimal on model creation
- Update CRUD layer tests to verify float params for SQLite
- Add SQLite round-trip tests to verify precision is preserved

The data flow is now:
Decimal (calculations) → float (prepare_for_db) → SQLite → float → Decimal (validators)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
padreug 2026-01-11 15:09:45 +01:00
parent d245047487
commit 904b3f1d61
3 changed files with 323 additions and 60 deletions

View file

@ -7,6 +7,16 @@ from typing import Optional
from pydantic import BaseModel, validator
def _to_decimal(v):
"""Convert a value to Decimal, handling floats from database."""
if v is None:
return None
if isinstance(v, Decimal):
return v
# Convert via string to avoid float precision issues
return Decimal(str(v))
# DCA Client Models
class CreateDcaClientData(BaseModel):
user_id: str
@ -27,6 +37,10 @@ class DcaClient(BaseModel):
created_at: datetime
updated_at: datetime
@validator('fixed_mode_daily_limit', pre=True)
def convert_fixed_mode_daily_limit(cls, v):
return _to_decimal(v)
class Config:
json_encoders = {Decimal: lambda v: float(v)}
@ -65,6 +79,10 @@ class DcaDeposit(BaseModel):
created_at: datetime
confirmed_at: Optional[datetime]
@validator('amount', pre=True)
def convert_amount(cls, v):
return _to_decimal(v)
class Config:
json_encoders = {Decimal: lambda v: float(v)}
@ -99,6 +117,10 @@ class DcaPayment(BaseModel):
created_at: datetime
transaction_time: Optional[datetime] = None # Original ATM transaction time
@validator('amount_fiat', 'exchange_rate', pre=True)
def convert_decimals(cls, v):
return _to_decimal(v)
class Config:
json_encoders = {Decimal: lambda v: float(v)}
@ -111,6 +133,10 @@ class ClientBalanceSummary(BaseModel):
remaining_balance: Decimal # Available balance for DCA in GTQ
currency: str
@validator('total_deposits', 'total_payments', 'remaining_balance', pre=True)
def convert_decimals(cls, v):
return _to_decimal(v)
class Config:
json_encoders = {Decimal: lambda v: float(v)}
@ -165,6 +191,13 @@ class StoredLamassuTransaction(BaseModel):
clients_count: int # Number of clients who received distributions
distributions_total_sats: int # Total sats distributed to clients
@validator(
'fiat_amount', 'commission_percentage', 'discount',
'effective_commission', 'exchange_rate', pre=True
)
def convert_decimals(cls, v):
return _to_decimal(v)
class Config:
json_encoders = {Decimal: lambda v: float(v)}
@ -228,6 +261,10 @@ class LamassuConfig(BaseModel):
# DCA Client Limits
max_daily_limit_gtq: Decimal = Decimal("2000") # Maximum daily limit for Fixed mode clients
@validator('max_daily_limit_gtq', pre=True)
def convert_max_daily_limit(cls, v):
return _to_decimal(v)
class Config:
json_encoders = {Decimal: lambda v: float(v)}