# SmartWallet — Architectural Documentation

## 1. Application Purpose

SmartWallet is a terminal application (TUI) for managing a household budget. It allows the user to organize income and expenses in a hierarchical tree structure — categories can be nested infinitely (e.g., Home -> Bills -> Electricity), and each leaf holds a transaction with an amount and date.

Data is stored locally in JSON format with schema versioning, ensuring safe migrations in future releases.

---

## 2. Design Patterns

### 2.1 Composite

**Problem:** A budget consists of categories (which can contain subcategories) and transactions (leaves). We need a uniform way to calculate balances — regardless of whether we are querying a single transaction or an entire branch of the tree.

**Solution:** A classic Composite pattern with three elements:

| Role | Class | File |
|------|-------|------|
| **Component** (interface) | `BudgetComponent` | `src/smartwallet/model/base.py` |
| **Composite** (node) | `Category` | `src/smartwallet/model/category.py` |
| **Leaf** | `Transaction` | `src/smartwallet/model/transaction.py` |

**How it works:**

```
BudgetComponent (ABC)
├── get_balance() -> dict    # abstract method
├── to_dict() -> dict        # abstract method
│
├── Category (Composite)
│   ├── children: list[BudgetComponent]
│   ├── add(component)       # adds a child
│   ├── remove(component_id) # removes a child by ID
│   ├── get_balance()        # RECURSIVELY sums children
│   └── to_dict()            # recursive serialization
│
└── Transaction (Leaf)
    ├── amount: float
    ├── transaction_date: str
    ├── get_balance()        # returns {income, expense, total}
    └── to_dict()            # flat serialization
```

The `get_balance()` method in `Category` iterates over all children and sums their balances recursively. This allows the client (UI) to query any tree node for its balance — without knowing whether it is a category with 50 subcategories or a single transaction.

**Usage example:**
```
Root
├── Work (Category)
│   └── Salary: +5000.00
├── Home (Category)
│   ├── Bills (Category)
│   │   ├── Electricity: -200.00
│   │   └── Water: -80.00
│   └── Rent: -1500.00
└── Food (Category)
    └── Grocery: -350.00

Root.get_balance() = {income: 5000.00, expense: -2130.00, total: 2870.00}
Home.get_balance() = {income: 0.00, expense: -1780.00, total: -1780.00}
```

---

### 2.2 Factory Method

**Problem:** Creating `Transaction` and `Category` objects requires input validation (non-empty name, numeric amount, valid date format). Validation logic should not be scattered across the entire application.

**Solution:** The `BudgetFactory` class in `src/smartwallet/factory/budget_factory.py` centralizes object creation:

```python
class BudgetFactory:
    @staticmethod
    def create_transaction(name, amount, trans_date=None) -> Transaction:
        # Validation: non-empty name, numeric and non-zero amount,
        # optional ISO date format. Default: today's date.

    @staticmethod
    def create_category(name) -> Category:
        # Validation: non-empty name.
```

**Validations performed by the factory:**
- Empty name → `ValueError`
- Non-numeric amount (e.g., `"abc"`) → `ValueError`
- Zero amount → `ValueError`
- Invalid date format → `ValueError`
- Missing date → automatically assigns today's date

This way the UI layer (modals) does not need to implement validation logic — it passes raw data to the factory, which either returns a ready object or raises an exception.

---

## 3. Data Structure — General Tree

The application uses a **General Tree** as its primary data structure. Each node (`Category`) can have any number of children — both subcategories and transactions.

```
Category (internal node)
├── children: list[BudgetComponent]  # list of children (categories + transactions)
├── add(component)                   # O(1) — append to list
├── remove(component_id)             # O(n) — search by ID
└── get_balance()                    # O(n) — traverse entire subtree (DFS)
```

Tree operations:
- **Adding** — `Category.add()` appends a new child to the list
- **Removing** — recursive DFS in `MainScreen._remove_component()` searches for the node by UUID and removes it from its parent
- **Balance calculation** — recursive DFS in `Category.get_balance()` sums income and expenses across the entire subtree
- **Serialization** — recursive `to_dict()` converts the tree into nested dictionaries (JSON)
- **Deserialization** — `_component_from_dict()` in `persistence.py` recursively reconstructs the tree from JSON

---

## 4. User Interface

The application uses the [Textual](https://textual.textualize.io/) framework to render the terminal user interface (TUI).

### Layout

Two-column layout:
- **Left column (1fr)** — `BudgetTree` — category and transaction tree with color-coded amounts
- **Right column (2fr)** — `DetailPanel` — details of the selected element (balance, type, date)

### UI Components

| Component | File | Description |
|-----------|------|-------------|
| `SmartWalletApp` | `ui/app.py` | Main Textual application, theme registration |
| `MainScreen` | `ui/screens/main_screen.py` | Screen with layout, keybindings, autosave |
| `BudgetTree` | `ui/widgets/budget_tree.py` | Tree widget with Rich markup |
| `DetailPanel` | `ui/widgets/detail_panel.py` | Reactive panel (Textual `reactive`) |
| `AddCategoryModal` | `ui/modals/add_category.py` | Add category dialog |
| `AddTransactionModal` | `ui/modals/add_transaction.py` | Add transaction dialog |
| `EditCategoryModal` | `ui/modals/edit_category.py` | Edit category dialog |
| `EditTransactionModal` | `ui/modals/edit_transaction.py` | Edit transaction dialog |
| `ConfirmDeleteModal` | `ui/modals/confirm_delete.py` | Delete confirmation dialog |

### Keybindings

| Key | Action |
|-----|--------|
| `a` | Add subcategory |
| `t` | Add transaction |
| `e` | Edit selected element |
| `d` | Delete selected element |
| `Tab` | Next tree node |
| `Shift+Tab` | Previous tree node |
| `q` | Quit application |

---

## 5. MVC Architecture

The application follows the **Model-View-Controller** pattern with a clear separation of layers:

```
┌────────────────────────────────────────────────────────────┐
│                     UI (View + Controller)                 │
│  ┌───────────┐  ┌───────────┐ ┌─────────────────────────┐  │
│  │ BudgetTree│  │DetailPanel│ │ Modals (Add/Edit/Delete)│  │
│  └────┬──────┘  └────┬──────┘ └───────────┬─────────────┘  │
│       │              │                    │                │
│       └──────────────┼────────────────────┘                │
│                      │                                     │
│              ┌───────┴───────┐                             │
│              │  MainScreen   │  <-- Controller             │
│              └───────┬───────┘                             │
└──────────────────────┼─────────────────────────────────────┘
                       │
          ┌────────────┼────────────┐
          │            │            │
    ┌─────┴──────┐ ┌───┴─────┐ ┌────┴──────┐
    │  Factory   │ │  Model  │ │Persistence│
    │(validation)│ │ (data)  │ │  (JSON)   │
    └────────────┘ └─────────┘ └───────────┘
```

### Data Flow

1. **User** presses a key (e.g., `t` — add transaction)
2. **Controller** (`MainScreen`) opens the `AddTransactionModal`
3. **View** (modal) collects data from the user and returns it to the controller
4. **Factory** (`BudgetFactory.create_transaction()`) validates data and creates the object
5. **Model** (`Category.add()`) adds the transaction to the appropriate category
6. **Persistence** (`save_budget()`) serializes the tree to JSON
7. **View** (`BudgetTree.rebuild()`, `DetailPanel`) refreshes the display

---

## 6. Data Persistence

Data is stored in `~/.smartwallet/budget.json` (configurable via the `SMARTWALLET_DATA_PATH` environment variable).

### JSON Format

```json
{
  "schema_version": 1,
  "data": {
    "type": "category",
    "name": "Root",
    "id": "uuid-...",
    "created_at": "2026-02-10T12:00:00",
    "children": [
      {
        "type": "transaction",
        "name": "Salary",
        "id": "uuid-...",
        "amount": 5000.0,
        "transaction_date": "2026-02-10"
      }
    ]
  }
}
```

The `schema_version` field enables future data format migrations without losing user data.

---

## 7. File Structure

```
smartwallet/
├── src/smartwallet/
│   ├── __init__.py
│   ├── main.py                  # CLI entry point
│   ├── config.py                # Data file path
│   ├── persistence.py           # JSON save/load
│   ├── model/
│   │   ├── base.py              # BudgetComponent (ABC)
│   │   ├── category.py          # Category (Composite)
│   │   └── transaction.py       # Transaction (Leaf)
│   ├── factory/
│   │   └── budget_factory.py    # BudgetFactory
│   └── ui/
│       ├── app.py               # SmartWalletApp
│       ├── styles/app.tcss      # TCSS stylesheet
│       ├── screens/
│       │   └── main_screen.py   # MainScreen (Controller)
│       ├── widgets/
│       │   ├── budget_tree.py   # BudgetTree (Tree widget)
│       │   └── detail_panel.py  # DetailPanel (Reactive)
│       └── modals/
│           ├── add_category.py
│           ├── add_transaction.py
│           ├── edit_category.py
│           ├── edit_transaction.py
│           └── confirm_delete.py
├── tests/unit/
│   ├── test_entrypoint.py       # 2 tests
│   ├── test_factory.py          # 6 tests
│   ├── test_model_balance.py    # 3 tests
│   ├── test_model_serialization.py  # 3 tests
│   └── test_persistence.py      # 13 tests
├── docs/
│   └── architecture.md          # This document
├── pyproject.toml
├── LICENSE
└── README.md
```

---

## 8. Tests

The project has **27 unit tests** covering the key layers:

| Test File | Test Count | Coverage |
|-----------|------------|----------|
| `test_entrypoint.py` | 2 | App instantiation, `run()` import |
| `test_factory.py` | 6 | Factory input validation |
| `test_model_balance.py` | 3 | Balance calculation (income, expense, recursive) |
| `test_model_serialization.py` | 3 | `to_dict()` serialization, element removal |
| `test_persistence.py` | 13 | Save/load, missing file, invalid JSON, schema version |

Run with: `uv run pytest`

---

## 9. Assignment Requirements — Summary

| Requirement | Implementation |
|-------------|----------------|
| Concrete, meaningful purpose | Household budget management (income/expenses) |
| Min. 2 design patterns | **Composite** (budget tree) + **Factory Method** (validation) |
| Min. 1 data structure | **General Tree** with recursive operations |
| User interface | **Textual TUI** with cyberpunk theme |
| Separated logic and UI | **MVC** — model/factory independent of UI |
| Data persistence (optional) | **JSON** with schema versioning |
| Tests (optional) | **27 tests** (pytest) |
| Modularity (optional) | **src-layout**, 4 independent modules |
| Documentation (optional) | This document + README |
