# Phase 3: Core Logic Refactoring - COMPLETE ✅

## Summary

Phase 3 of the Beancount-inspired refactor focused on **separating business logic from database operations** and creating a clean, testable core module. This phase improves code quality, maintainability, and follows best practices from Beancount's architecture.

## Completed Features

### 1. Core Module Structure ✅

**Purpose**: Separate pure accounting logic from database and API concerns

**Implementation** (`core/__init__.py`):
- Created `core/` module package
- Exports main classes and functions
- Clean separation of concerns

**Benefits**:
- Testable without database
- Reusable across different storage backends
- Easier to audit and verify
- Clear architecture

### 2. CastleInventory for Position Tracking ✅

**Purpose**: Track balances across multiple currencies with cost basis information (following Beancount's Inventory pattern)

**Implementation** (`core/inventory.py`):

**CastlePosition** (Lines 11-84):
- Immutable dataclass representing a single position
- Tracks currency, amount, cost basis, and metadata
- Supports addition and negation operations
- Automatic Decimal conversion in `__post_init__`

```python
@dataclass(frozen=True)
class CastlePosition:
    currency: str  # "SATS", "EUR", "USD"
    amount: Decimal
    cost_currency: Optional[str] = None
    cost_amount: Optional[Decimal] = None
    date: Optional[datetime] = None
    metadata: Dict[str, Any] = field(default_factory=dict)
```

**CastleInventory** (Lines 87-201):
- Container for multiple positions
- Positions keyed by `(currency, cost_currency)` tuple
- Methods for querying balances:
  - `get_balance_sats()` - Total satoshis
  - `get_balance_fiat(currency)` - Fiat balance for specific currency
  - `get_all_fiat_balances()` - All fiat balances
- Utility methods:
  - `is_empty()` - Check if no positions
  - `is_zero()` - Check if all positions sum to zero
  - `to_dict()` - Export to dictionary

### 3. BalanceCalculator ✅

**Purpose**: Pure logic for calculating balances from journal entries

**Implementation** (`core/balance.py`):

**AccountType Enum** (Lines 13-19):
```python
class AccountType(str, Enum):
    ASSET = "asset"
    LIABILITY = "liability"
    EQUITY = "equity"
    REVENUE = "revenue"
    EXPENSE = "expense"
```

**BalanceCalculator Class** (Lines 22-217):

**Static Methods**:

1. **`calculate_account_balance()`** (Lines 29-54):
   - Calculate balance based on account type
   - Normal balances:
     - Assets/Expenses: Debit balance (debit - credit)
     - Liabilities/Equity/Revenue: Credit balance (credit - debit)

2. **`build_inventory_from_entry_lines()`** (Lines 56-117):
   - Build CastleInventory from journal entry lines
   - Handles both sats and fiat currency tracking
   - Accounts for account type when determining sign

3. **`calculate_user_balance()`** (Lines 119-168):
   - Calculate user's total balance across all accounts
   - Returns both sats balance and fiat balances by currency
   - Properly handles asset (receivable) vs liability (payable) accounts

4. **`check_balance_matches()`** (Lines 170-187):
   - Verify balance assertion for sats

5. **`check_fiat_balance_matches()`** (Lines 189-202):
   - Verify balance assertion for fiat currency

### 4. Comprehensive Validation ✅

**Purpose**: Validation rules for accounting operations

**Implementation** (`core/validation.py`):

**ValidationError Exception** (Lines 10-18):
- Custom exception for validation failures
- Includes detailed error information

**Validation Functions**:

1. **`validate_journal_entry()`** (Lines 21-124):
   - Checks:
     - At least 2 lines (double-entry requirement)
     - Entry is balanced (debits = credits)
     - Valid amounts (non-negative)
     - No line has both debit and credit
     - All lines have account_id

2. **`validate_balance()`** (Lines 127-177):
   - Validates balance assertions
   - Checks both sats and fiat within tolerance

3. **`validate_receivable_entry()`** (Lines 180-199):
   - Validates receivable (user owes castle) entries
   - Ensures positive amount
   - Ensures revenue account type

4. **`validate_expense_entry()`** (Lines 202-227):
   - Validates expense entries
   - Ensures positive amount
   - Checks account type (expense or equity)

5. **`validate_payment_entry()`** (Lines 230-245):
   - Validates payment entries
   - Ensures positive amount

6. **`validate_metadata()`** (Lines 248-284):
   - Validates entry line metadata
   - Checks for required keys
   - Validates fiat currency/amount consistency
   - Validates Decimal conversion

### 5. Refactored CRUD Operations ✅

**Purpose**: Use core logic in database operations

**Modified Files**: `crud.py`

**Changes**:

1. **Imports** (Lines 26-36):
   - Import core accounting logic
   - Import validation functions

2. **`get_account_balance()`** (Lines 347-377):
   - Refactored to use `BalanceCalculator.calculate_account_balance()`
   - Removed duplicate logic

3. **`get_user_balance()`** (Lines 380-435):
   - Completely refactored to use:
     - `BalanceCalculator.build_inventory_from_entry_lines()`
     - `BalanceCalculator.calculate_user_balance()`
   - Cleaner separation of database queries vs business logic

4. **`get_all_user_balances()`** (Lines 438-459):
   - Simplified to call `get_user_balance()` for each user
   - Eliminates code duplication

## Architecture

### Before Phase 3

```
views_api.py → crud.py (mixed DB + logic)
                  ↓
               database
```

All accounting logic was embedded in crud.py alongside database operations.

### After Phase 3

```
views_api.py → crud.py → core/
                  ↓         ↓
              database   Pure Logic
                         (testable)
```

**Separation of Concerns**:
- `core/` - Pure accounting logic (no DB dependencies)
- `crud.py` - Database operations + orchestration
- `views_api.py` - HTTP API layer

## Benefits

### Code Quality
- ✅ **Testability**: Core logic can be tested without database
- ✅ **Maintainability**: Clear separation makes code easier to understand
- ✅ **Reusability**: Core logic can be used in different contexts
- ✅ **Consistency**: Centralized accounting rules

### Developer Experience
- ✅ **Type Safety**: Immutable dataclasses with proper types
- ✅ **Documentation**: Well-documented core functions
- ✅ **Debugging**: Easier to trace accounting logic
- ✅ **Refactoring**: Safer to make changes

### Reliability
- ✅ **Validation**: Comprehensive validation rules
- ✅ **Correctness**: Pure functions easier to verify
- ✅ **Auditability**: Clear accounting rules

## File Structure

```
lnbits/extensions/castle/
├── core/
│   ├── __init__.py          # Module exports
│   ├── inventory.py         # CastleInventory, CastlePosition
│   ├── balance.py           # BalanceCalculator
│   └── validation.py        # Validation functions
├── crud.py                  # DB operations (refactored to use core/)
├── models.py                # Pydantic models
├── views_api.py             # API endpoints
└── PHASE3_COMPLETE.md       # This file
```

## Usage Examples

### Using CastleInventory

```python
from decimal import Decimal
from castle.core.inventory import CastleInventory, CastlePosition

# Create inventory
inv = CastleInventory()

# Add positions
inv.add_position(CastlePosition(
    currency="SATS",
    amount=Decimal("100000")
))

inv.add_position(CastlePosition(
    currency="SATS",
    amount=Decimal("50000"),
    cost_currency="EUR",
    cost_amount=Decimal("25.00")
))

# Query balances
total_sats = inv.get_balance_sats()  # Decimal("150000")
eur_balance = inv.get_balance_fiat("EUR")  # Decimal("25.00")

# Export
data = inv.to_dict()
# {"sats": 150000, "fiat": {"EUR": 25.00}}
```

### Using BalanceCalculator

```python
from castle.core.balance import BalanceCalculator, AccountType

# Calculate account balance
balance = BalanceCalculator.calculate_account_balance(
    total_debit=100000,
    total_credit=50000,
    account_type=AccountType.ASSET
)
# Returns: 50000 (debit balance for asset)

# Build inventory from entry lines
entry_lines = [
    {"debit": 100000, "credit": 0, "metadata": '{"fiat_currency": "EUR", "fiat_amount": "50.00"}'},
    {"debit": 0, "credit": 50000, "metadata": "{}"}
]

inventory = BalanceCalculator.build_inventory_from_entry_lines(
    entry_lines,
    AccountType.ASSET
)

# Check balance matches
is_valid = BalanceCalculator.check_balance_matches(
    actual_balance_sats=100000,
    expected_balance_sats=99900,
    tolerance_sats=100
)
# Returns: True (within tolerance)
```

### Using Validation

```python
from castle.core.validation import validate_journal_entry, ValidationError

entry = {
    "id": "abc123",
    "description": "Test entry",
    "entry_date": datetime.now()
}

entry_lines = [
    {"account_id": "acc1", "debit": 100000, "credit": 0},
    {"account_id": "acc2", "debit": 0, "credit": 100000}
]

try:
    validate_journal_entry(entry, entry_lines)
    print("Valid!")
except ValidationError as e:
    print(f"Invalid: {e.message}")
    print(f"Details: {e.details}")
```

## Testing Checklist

- [x] CastleInventory created and tested
- [x] CastlePosition addition works
- [x] Inventory balance calculations work
- [x] BalanceCalculator account balance calculation works
- [x] BalanceCalculator inventory building works
- [x] BalanceCalculator user balance calculation works
- [x] Validation functions work
- [x] crud.py refactored to use core logic
- [x] Existing balance calculations still work
- [ ] Unit tests for core module (future work)

## Next Steps

**Phase 4: Validation Plugins** (Medium Priority)
- Create plugin system architecture
- Implement `check_balanced` plugin
- Implement `check_receivables` plugin
- Add plugin configuration UI

**Future Enhancements**:
- Add unit tests for core/ module
- Add integration tests
- Add lot tracking to inventory
- Support multi-currency in single entry
- Add more validation plugins

## Conclusion

Phase 3 successfully refactors Castle's accounting logic into a clean, testable core module. By following Beancount's architecture patterns, we've created:

- **Pure accounting logic** separated from database concerns
- **CastleInventory** for position tracking across currencies
- **BalanceCalculator** for consistent balance calculations
- **Comprehensive validation** for data integrity

The refactoring improves code quality, maintainability, and sets the foundation for Phase 4's plugin system.

**Phase 3 Status**: ✅ COMPLETE

---

*Generated: 2025-10-23*
*Next: Phase 4 - Validation Plugins*
