System Architecture
PVTools follows a layered architecture designed for extensibility, maintainability, and performance. This document outlines the key architectural decisions and design patterns.
High-Level Architecture
┌─────────────────────────────────────────────────────────────┐
│ CLI/API Interface │
├─────────────────────────────────────────────────────────────┤
│ Optimization Engine │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ Tokenization │ │ Term Loan │ │ Direct │ │
│ │ Optimizer │ │ Optimizer │ │ Purchase │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Core Business Logic │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ Production │ │ Financial │ │ BESS │ │
│ │ Modeling │ │ Modeling │ │ Modeling │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Utilities Layer │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ Technical │ │ Processes │ │ Validation │ │
│ │ Calculations │ │ & Data │ │ & Tools │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ External Services │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ PVGIS │ │ Forecast.Solar │ │ TNB │ │
│ │ API │ │ API │ │ Tariffs │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Core Components
1. Optimization Engine (optimiser.py)
The main entry point that orchestrates the optimization process:
class OptimizationEngine:
"""Main optimization coordinator."""
def optimize(self, config: Dict, consumption_profile: Optional[List]) -> OptimizationResult:
"""Run complete optimization pipeline."""
# 1. Data preparation
# 2. Load profile estimation
# 3. Financing model selection
# 4. Multi-objective optimization
# 5. Results compilation
Key Responsibilities:
- Configuration validation and setup
- Financing model selection and dispatch
- Result aggregation and ESG calculations
- Error handling and logging
2. Production Modeling (production_modeling.py)
Handles solar energy production calculations and forecasting:
class ProductionModel:
"""Solar production modeling and forecasting."""
def historical_monthly_avg(self) -> pd.DataFrame:
"""Get historical monthly production averages."""
def historical_hourly_power(self) -> pd.DataFrame:
"""Get detailed hourly production data."""
def forecast_production(self) -> pd.DataFrame:
"""Generate production forecasts."""
Key Responsibilities:
- PVGIS API integration for historical data
- Forecast.Solar API integration for predictions
- Production uncertainty modeling
- Weather data processing
3. Financing Optimizers (optimizers/)
Modular financing calculation engines:
optimizers/
├── tokenization/
│ ├── __init__.py
│ ├── PPA.py # Power Purchase Agreement
│ └── DirectPurchase.py # Direct tokenized purchase
└── term_loan/
├── __init__.py
├── PPA.py # Term loan PPA
└── DirectPurchase.py # Term loan direct purchase
Each optimizer implements the common interface:
class FinancingOptimizer(ABC):
"""Abstract base class for financing optimizers."""
@abstractmethod
def optimize(self, config: Dict, consumption: List) -> Tuple[Dict, Dict]:
"""Run financing-specific optimization."""
@abstractmethod
def calculate_roi(self, system_size: float) -> float:
"""Calculate return on investment."""
4. Utilities Layer (utilities/)
Core technical and financial calculation modules:
Technical Utilities (utilities/technical.py)
- TNB tariff calculations
- Grid integration modeling
- Inverter sizing algorithms
- Bill calculation engines
Financial Utilities (utilities/finance.py)
- NPV/IRR calculations
- Cash flow modeling
- Tax incentive calculations
- Depreciation schedules
BESS Modeling (utilities/bess.py)
- Battery sizing optimization
- State of charge calculations
- Backup power modeling
- Grid arbitrage strategies
Data Processing (utilities/processes.py)
- API data fetching and caching
- Geographic coordinate resolution
- Data aggregation and formatting
- Time series processing
Design Patterns
1. Strategy Pattern (Financing Models)
Different financing models are implemented as strategies:
class OptimizationContext:
def __init__(self, financing_strategy: FinancingOptimizer):
self._strategy = financing_strategy
def optimize(self, config: Dict) -> OptimizationResult:
return self._strategy.optimize(config)
2. Factory Pattern (Model Selection)
Dynamic model instantiation based on configuration:
def create_optimizer(financing_method: str, contract_type: str) -> FinancingOptimizer:
"""Factory function for optimizer creation."""
module_path = f"pv_tools.optimizers.{financing_method}.{contract_type}"
module = __import__(module_path, fromlist=[contract_type])
return getattr(module, contract_type)()
3. Builder Pattern (Configuration)
Complex configuration building with validation:
class ConfigBuilder:
"""Builder for optimization configuration."""
def with_location(self, lat: float, lon: float) -> 'ConfigBuilder':
self._config['location'] = {'lat': lat, 'lon': lon}
return self
def with_financing(self, method: str) -> 'ConfigBuilder':
self._config['financing'] = method
return self
def build(self) -> Dict:
self._validate()
return self._config
Data Flow Architecture
1. Configuration Flow
YAML Config → Validation → Data Loading → Model Setup
↓ ↓ ↓ ↓
User Input → Schema Check → JSON Files → Runtime Config
2. Optimization Flow
Config → Production Model → Load Estimation → Optimization → Results
↓ ↓ ↓ ↓ ↓
Runtime → Solar Data → Consumption Profile → Algorithm → Financial Model
3. Data Sources Integration
External APIs → Caching Layer → Data Processing → Model Input
↓ ↓ ↓ ↓
PVGIS/TNB → Local Storage → Normalization → Calculations
Performance Considerations
1. Caching Strategy
- API Response Caching: Local storage of PVGIS/Forecast.Solar data
- Computation Caching: Memoization of expensive calculations
- Configuration Caching: Parsed configuration reuse
2. Optimization Performance
- Gradient-based Methods: SciPy optimization for smooth objectives
- Constraint Handling: Efficient constraint evaluation
- Parallel Processing: Multi-threaded scenario evaluation
3. Memory Management
- Lazy Loading: Load data only when needed
- Generator Patterns: Stream processing for large datasets
- Cleanup: Explicit resource cleanup after optimization
Extension Points
1. Adding New Financing Models
- Create new directory under
optimizers/ - Implement
FinancingOptimizerinterface - Add configuration validation
- Update factory function
2. Adding New Utility Functions
- Choose appropriate utility module
- Follow type annotation standards
- Add comprehensive docstrings
- Include usage examples
3. Adding New Data Sources
- Implement data fetching in
utilities/processes.py - Add caching logic
- Update configuration schema
- Add integration tests
Error Handling Strategy
1. Hierarchical Exception Handling
class PVToolsError(Exception):
"""Base exception for PVTools."""
class OptimizationError(PVToolsError):
"""Optimization-specific errors."""
class DataError(PVToolsError):
"""Data processing errors."""
2. Graceful Degradation
- Fallback to cached data when APIs fail
- Default parameters for missing configuration
- Partial results when optimization fails
3. Comprehensive Logging
- Structured logging with context
- Performance metrics tracking
- Error context preservation
Security Considerations
1. API Key Management
- Environment variable storage
- Key rotation support
- Rate limiting compliance
2. Input Validation
- Configuration schema validation
- API response validation
- Numeric range checking
3. Data Privacy
- No persistent storage of sensitive data
- Anonymized logging
- Secure API communication