Skip to main content

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​

  1. Create new directory under optimizers/
  2. Implement FinancingOptimizer interface
  3. Add configuration validation
  4. Update factory function

2. Adding New Utility Functions​

  1. Choose appropriate utility module
  2. Follow type annotation standards
  3. Add comprehensive docstrings
  4. Include usage examples

3. Adding New Data Sources​

  1. Implement data fetching in utilities/processes.py
  2. Add caching logic
  3. Update configuration schema
  4. 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