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