Source code for kpicalculator.calculators.energy_calculator
# src/kpicalculator/calculators/energy_calculator.py
import logging
from ..adapters.common_model import Asset, AssetType, EnergySystem
from ..common.constants import (
CONSUMPTION_FIELDS,
DEMAND_FIELDS,
PRODUCTION_FIELDS,
SECONDS_PER_YEAR,
)
logger = logging.getLogger(__name__)
[docs]
class EnergyCalculator:
"""Calculator for energy-related KPIs."""
def __init__(self, energy_system: EnergySystem):
"""Initialize the energy calculator.
Args:
energy_system: Energy system to calculate KPIs for
"""
self.energy_system = energy_system
[docs]
def get_total_energy_consumption_per_year(self) -> float:
"""Calculate total energy consumption per year.
Returns:
Total energy consumption in joules per year
"""
total_consumption = 0.0
for asset in self.energy_system.assets:
if asset.asset_type == AssetType.CONSUMER:
total_consumption += self._calculate_asset_energy_consumption(asset)
return total_consumption
[docs]
def get_total_energy_demand_per_year(self) -> float:
"""Calculate total energy demand per year.
Returns:
Total energy demand in joules per year
"""
total_demand = 0.0
for asset in self.energy_system.assets:
if asset.asset_type == AssetType.CONSUMER:
total_demand += self._calculate_asset_energy_demand(asset)
return total_demand
[docs]
def get_total_energy_production_per_year(self) -> float:
"""Calculate total energy production per year.
Returns:
Total energy production in joules per year
"""
total_production = 0.0
for asset in self.energy_system.assets:
if asset.asset_type in [AssetType.PRODUCER, AssetType.GEOTHERMAL]:
total_production += self._calculate_asset_energy_production(asset)
return total_production
[docs]
def calculate_system_efficiency(self) -> float:
"""Calculate overall system efficiency.
Returns:
System efficiency as a ratio (0-1)
"""
production = self.get_total_energy_production_per_year()
consumption = self.get_total_energy_consumption_per_year()
if production <= 0:
return 0.0
return consumption / production
def _calculate_asset_energy_consumption(self, asset: Asset) -> float:
"""Calculate energy consumption for a specific asset.
Args:
asset: Asset to calculate energy consumption for
Returns:
Energy consumption in joules per year
"""
if not asset.time_series:
logger.debug(
"No time series data for asset '%s'. Consumption returned as 0.0.",
asset.name,
)
return 0.0
# Look for consumption time series
ts_name = None
for name in CONSUMPTION_FIELDS:
if name in asset.time_series:
ts_name = name
break
if not ts_name:
logger.debug(
"No consumption field found in time series for asset '%s'. Returning 0.0.",
asset.name,
)
return 0.0
ts = asset.time_series[ts_name]
# Calculate annual energy
duration = ts.time_step * len(ts.values)
if duration <= 0:
logger.warning(
"Non-positive duration in consumption time series for asset '%s'. Returning 0.0.",
asset.name,
)
return 0.0
time_factor = SECONDS_PER_YEAR / duration
energy_sum = sum(ts.values) * ts.time_step
return energy_sum * time_factor
def _calculate_asset_energy_demand(self, asset: Asset) -> float:
"""Calculate energy demand for a specific asset.
Args:
asset: Asset to calculate energy demand for
Returns:
Energy demand in joules per year
"""
if not asset.time_series:
logger.debug(
"No time series data for asset '%s'. Demand returned as 0.0.",
asset.name,
)
return 0.0
# Look for demand time series
ts_name = None
for name in DEMAND_FIELDS:
if name in asset.time_series:
ts_name = name
break
if not ts_name:
return self._calculate_asset_energy_consumption(asset) # Fall back to consumption
ts = asset.time_series[ts_name]
# Calculate annual energy
duration = ts.time_step * len(ts.values)
if duration <= 0:
logger.warning(
"Non-positive duration in demand time series for asset '%s'. Returning 0.0.",
asset.name,
)
return 0.0
time_factor = SECONDS_PER_YEAR / duration
energy_sum = sum(ts.values) * ts.time_step
return energy_sum * time_factor
[docs]
def get_asset_energy_production_per_year(self, asset: Asset) -> float:
"""Calculate annual energy production for a single producing asset.
Args:
asset: Producing asset (PRODUCER or GEOTHERMAL type).
Returns:
Energy production in joules per year, or 0.0 if no time series data.
"""
return self._calculate_asset_energy_production(asset)
def _calculate_asset_energy_production(self, asset: Asset) -> float:
"""Calculate energy production for a specific asset.
Args:
asset: Asset to calculate energy production for
Returns:
Energy production in joules per year
"""
if not asset.time_series:
logger.debug(
"No time series data for asset '%s'. Production returned as 0.0.",
asset.name,
)
return 0.0
# Look for production time series
ts_name = None
for name in PRODUCTION_FIELDS:
if name in asset.time_series:
ts_name = name
break
if not ts_name:
logger.debug(
"No production field found in time series for asset '%s'. Returning 0.0.",
asset.name,
)
return 0.0
ts = asset.time_series[ts_name]
# Calculate annual energy
duration = ts.time_step * len(ts.values)
if duration <= 0:
logger.warning(
"Non-positive duration in production time series for asset '%s'. Returning 0.0.",
asset.name,
)
return 0.0
time_factor = SECONDS_PER_YEAR / duration
energy_sum = sum(ts.values) * ts.time_step
return energy_sum * time_factor