Architecture Overview¶
Kanso is built with modern Python async architecture, emphasizing simplicity, performance, and maintainability.
Technology Stack¶
Backend¶
- Python 3.13 - Modern async features, type hints
- NiceGUI - Reactive web framework built on FastAPI
- FastAPI - High-performance async API framework
- Pandas - Data manipulation and analysis
- Google API Client - Google Sheets integration
Frontend¶
- NiceGUI Components - Reactive UI elements
- ECharts - Interactive data visualizations
- Tailwind CSS - Utility-first styling via DaisyUI
Infrastructure¶
- Docker - Containerized deployment
- uv - Fast Python package management
- pytest - Comprehensive testing (extensive test coverage)
High-Level Architecture¶
graph TB
Browser[Web Browser] -->|HTTP| NiceGUI[NiceGUI Server]
NiceGUI -->|Read/Write| Storage[Browser Storage]
NiceGUI -->|Fetch Data| Service[FinanceService]
Service -->|Load Sheets| Loader[DataLoader]
Loader -->|API Calls| Sheets[Google Sheets API]
Service -->|Process| Calculator[FinanceCalculator]
Calculator -->|Preprocess| Processor[DataFrameProcessor]
Calculator -->|Parse| Monetary[MonetaryParsing]
Calculator -->|Return| Service
Service -->|Render| Charts[ChartBuilder]
Charts -->|JSON| NiceGUI
Core Components¶
1. UI Layer (app/ui/)¶
Responsibility: User interface and routing
Key Files:
- main.py - Application entry point, routing
- home.py - Dashboard with KPIs and charts
- onboarding.py - First-time setup wizard
- user.py - Settings and preferences
- charts.py - Chart configuration and formatting
Pattern: Page-based routing with NiceGUI decorators
@ui.page('/')
async def index():
# Check if onboarding completed
if not onboarding_complete():
ui.navigate.to('/onboarding')
else:
ui.navigate.to('/home')
2. Services Layer (app/services/)¶
Responsibility: Business logic and data orchestration
Key Files:
- finance_service.py - Centralized data access layer
- data_loader.py - Google Sheets integration
Design Pattern: Service layer pattern for separation of concerns
class FinanceService:
def get_dashboard_data(self) -> dict[str, Any]:
"""Single call to get all KPIs + charts."""
calculator = self.get_calculator()
return {
"kpi_data": self._calculate_kpis(calculator),
"chart_data": self._prepare_charts(calculator),
}
Benefits: - UI layer stays thin and focused on presentation - Business logic is testable independently - Single source of truth for data access
3. Logic Layer (app/logic/)¶
Responsibility: Core financial calculations and data processing
Key Modules:
finance_calculator.py¶
Central calculation engine for financial metrics: - Net worth tracking (current, MoM, YoY) - Savings ratio calculation - Cash flow analysis - Chart data preparation
class FinanceCalculator:
def get_current_net_worth(self) -> float:
"""Calculate latest net worth."""
def get_monthly_net_worth(self) -> list[dict]:
"""Time series for net worth chart."""
dataframe_processor.py¶
Preprocessing utilities for financial DataFrames: - Date parsing and normalization - Column structure detection (single vs multi-index) - Monetary value summation - DataFrame sorting and cleaning
class DataFrameProcessor:
@staticmethod
def preprocess_expenses(df) -> pd.DataFrame:
"""Parse dates and amounts, sort by date."""
@staticmethod
def find_date_column(df) -> str | tuple:
"""Find Date column in single or MultiIndex."""
monetary_parsing.py¶
Currency detection and value parsing: - Multi-currency support for major world currencies - Automatic format detection (1.234,56 vs 1,234.56) - Robust error handling
def parse_monetary_value(value: Any, currency: str = None) -> float:
"""Parse monetary value with intelligent currency detection."""
def detect_currency(value: str) -> str | None:
"""Detect currency from symbol in string."""
data_loader.py¶
Google Sheets integration with smart caching: - Hash-based change detection - Granular sheet-level refresh - Error handling and retry logic
4. Core Layer (app/core/)¶
Responsibility: Shared utilities and constants
Key Modules:
constants.py¶
Centralized constants for consistency:
- Column names (COL_DATE, COL_AMOUNT)
- Date formats (DATE_FORMAT_STORAGE)
- Financial thresholds (SAVING_RATIO_THRESHOLD_LOW)
currency_formats.py¶
Currency configuration: - Symbol mappings (€, $, £, Fr, ¥) - Number format specs (decimal/thousands separators) - Immutable configuration with Pydantic
@dataclass(frozen=True)
class CurrencyFormat:
symbol: str
decimal_sep: str
thousands_sep: str
has_decimals: bool
CURRENCY_FORMATS = {
"EUR": CurrencyFormat("€", ",", ".", True),
"USD": CurrencyFormat("$", ".", ",", True),
# ...
}
state_manager.py¶
Intelligent caching for performance: - TTL-based cache expiration (24 hours) - Hash-based invalidation - Cache statistics
5. Data Validation (app/core/validators.py)¶
Responsibility: Data validation with Pydantic
Key Validators:
- ExpenseRow - Validated expense transaction
- AssetRow, LiabilityRow, IncomeRow - Financial data validation
class ExpenseRow(BaseModel):
date: str
merchant: str
amount: str
category: str
type: str
@field_validator('date')
def validate_date(cls, v):
# Ensure YYYY-MM format
Data Flow¶
Loading Financial Data¶
sequenceDiagram
participant UI
participant Service
participant Loader
participant Sheets
participant Cache
UI->>Service: get_dashboard_data()
Service->>Loader: load_all_sheets()
Loader->>Cache: Check cached data
alt Cache valid
Cache-->>Loader: Return cached DataFrames
else Cache expired/invalid
Loader->>Sheets: Fetch sheet data
Sheets-->>Loader: Raw values
Loader->>Cache: Store with hash
Cache-->>Loader: DataFrames
end
Loader-->>Service: {assets_df, liabilities_df, ...}
Service->>Calculator: FinanceCalculator(dfs)
Calculator->>Processor: preprocess_*()
Processor-->>Calculator: Processed DataFrames
Calculator->>Service: KPIs + Charts
Service-->>UI: {kpi_data, chart_data}
UI->>Browser: Render dashboard
Processing Pipeline¶
- Load - Fetch raw data from Google Sheets
- Parse - Convert to pandas DataFrames
- Preprocess - Normalize dates, parse amounts, sort
- Calculate - Compute KPIs and aggregations
- Format - Prepare data for charts
- Render - Display in UI
Performance Optimizations¶
1. Lazy Preprocessing¶
DataFrames are preprocessed on first access and cached:
@property
def processed_expenses_df(self) -> pd.DataFrame:
if self._processed_expenses_df is None:
self._processed_expenses_df = DataFrameProcessor.preprocess_expenses(
self.expenses_df
)
return self._processed_expenses_df
2. Unified Data Loading¶
Single calculator instance for all dashboard data:
# ❌ Old: Multiple instances
kpi_data = service.get_kpi_data() # Creates calculator
chart_data = service.get_chart_data() # Creates calculator again
# ✅ New: Single instance
dashboard_data = service.get_dashboard_data() # One calculator
3. Vectorized Operations¶
Pandas operations instead of row iteration:
# ❌ Old: Slow iterrows()
for _, row in df.iterrows():
total += parse_value(row['amount'])
# ✅ New: Vectorized groupby()
df.groupby('category')['amount_parsed'].sum()
4. Smart Caching¶
- Hash-based invalidation: Only refresh changed sheets
- TTL cache: 24-hour expiration for calculations
- In-memory storage: Fast repeated access
Testing Strategy¶
Unit Tests (extensive unit tests)¶
Coverage:
- Core logic (finance_calculator, dataframe_processor)
- Data validation (validators)
- Utilities (monetary_parsing, currency_formats)
Pattern:
class TestFinanceCalculator:
def test_get_current_net_worth(self):
calc = FinanceCalculator(assets_df=..., liabilities_df=...)
assert calc.get_current_net_worth() == 50000.0
E2E Tests¶
Coverage: - Homepage rendering with mock data (smoke tests) - Complete onboarding flow with validation - User settings and configuration updates - Theme toggling and currency selection - Credentials management and validation
Tools: Playwright for browser automation
Test Suites:
- test_home_smoke.py - Dashboard rendering and data display
- test_onboarding.py - First-time user setup flow
- test_user_settings.py - Settings page functionality
Security Considerations¶
Data Storage¶
- Browser storage: Credentials stored in browser local storage
- No server-side storage: Credentials never stored on server
- Service account: Google Sheets access via service account
Session Management¶
- HTTP-only cookies: Prevent XSS attacks
- CSRF protection: Built into FastAPI/NiceGUI
- Browser-based sessions: Sessions tied to browser storage
Google API¶
- Service account: No user OAuth flow
- Minimal scopes: Only Google Sheets API
- Audit logs: Track all API calls in Google Cloud Console
Deployment Architecture¶
Docker Container¶
kanso/
├── app/ # Application code
├── tests/ # Test suite
├── static/ # CSS, images
├── .env # Configuration (not in image)
├── Dockerfile # Multi-stage build
└── docker-compose.yml # Orchestration
Multi-stage build: 1. Builder stage: Install dependencies with uv 2. Runtime stage: Copy only necessary files 3. Result: Optimized, minimal image
Data Persistence¶
- Survives container restarts
- Contains preprocessed DataFrames
- Stores hash-based change detection state
Next Steps¶
- API Reference - Deep dive into classes and methods
- Contributing - Learn how to contribute
- Configuration - Customize Kanso settings