Source code for kpicalculator.calculators.emission_calculator
# src/kpicalculator/calculators/emission_calculator.py
import logging
import warnings
from ..adapters.common_model import Asset, AssetType, EnergySystem
from ..common.constants import (
CONSUMPTION_FIELDS,
CONVERSION_FIELDS,
KG_TO_TONS,
PRODUCTION_FIELDS,
SECONDS_PER_YEAR,
TONS_TO_KG,
)
logger = logging.getLogger(__name__)
[docs]
class EmissionCalculator:
"""Calculator for emission-related KPIs."""
def __init__(self, energy_system: EnergySystem):
"""Initialize the emission calculator.
Args:
energy_system: Energy system to calculate KPIs for
"""
self.energy_system = energy_system
# def get_total_emissions(self) -> float:
# """Calculate total CO2 emissions.
# Returns:
# Total CO2 emissions in tons per year
# """
# total_emissions = 0.0
# for asset in self.energy_system.assets:
# total_emissions += self._calculate_asset_emissions(asset)
# return total_emissions
[docs]
def get_total_emissions(self) -> float:
"""Calculate total CO2 emissions.
Returns:
Total CO2 emissions in tons per year
"""
total_emissions = 0.0
for asset in self.energy_system.assets:
total_emissions += self._calculate_asset_emissions(asset)
return total_emissions
[docs]
def get_emissions_per_mwh(self) -> float:
"""Calculate CO2 emissions per MWh of energy consumed.
Returns:
CO2 emissions in kg/MWh
"""
from .energy_calculator import EnergyCalculator
energy_calc = EnergyCalculator(self.energy_system)
energy_consumption = energy_calc.get_total_energy_consumption_per_year()
if energy_consumption <= 0:
return 0.0
# Convert energy from J to MWh (1 MWh = 3.6e9 J)
energy_consumption_mwh = energy_consumption / 3.6e9
# Convert emissions from tons to kg
emissions_kg = self.get_total_emissions() * TONS_TO_KG
return emissions_kg / energy_consumption_mwh
[docs]
def get_emissions_per_energy_unit(self) -> float:
"""Calculate CO2 emissions per GJ of energy consumed.
Returns:
CO2 emissions in kg/GJ
Warning:
This method is not yet wired into KpiManager.calculate_all_kpis().
Results are not included in the standard KPI output. Call
get_emissions_per_mwh() for the equivalent metric that is part of
the public API.
"""
warnings.warn(
"get_emissions_per_energy_unit() is not yet integrated into the KPI output. "
"Use get_emissions_per_mwh() for the equivalent metric available via KpiManager.",
UserWarning,
stacklevel=2,
)
from .energy_calculator import EnergyCalculator
energy_calc = EnergyCalculator(self.energy_system)
energy_consumption = energy_calc.get_total_energy_consumption_per_year()
if energy_consumption <= 0:
return 0.0
# Convert energy from J to GJ (1 GJ = 1e9 J)
energy_consumption_gj = energy_consumption / 1e9
# Convert emissions from tons to kg
emissions_kg = self.get_total_emissions() * TONS_TO_KG
return emissions_kg / energy_consumption_gj
def _calculate_asset_emissions(self, asset: Asset, annualize: bool = True) -> float:
"""Calculate CO2 emissions for a specific asset.
Args:
asset: Asset to calculate emissions for
annualize: If True, scale emissions to a full year
Returns:
CO2 emissions in tons (annualized or for actual period)
"""
if not asset.time_series:
logger.debug(
"No time series data for asset '%s'. Emissions returned as 0.0.",
asset.name,
)
return 0.0
# Select the correct time series key for the asset
ts_options = {
AssetType.PRODUCER: PRODUCTION_FIELDS,
AssetType.GEOTHERMAL: PRODUCTION_FIELDS,
AssetType.CONSUMER: CONSUMPTION_FIELDS,
AssetType.CONVERSION: CONVERSION_FIELDS,
}
ts_name = None
options: tuple[str, ...] = ts_options.get(asset.asset_type, ())
for key in options:
if key in asset.time_series:
ts_name = key
break
if not ts_name:
logger.debug(
"No matching time series field for asset '%s'. Emissions returned as 0.0.",
asset.name,
)
return 0.0
ts = asset.time_series[ts_name]
duration = ts.time_step * len(ts.values) # seconds
if duration <= 0:
logger.warning(
"Non-positive duration in time series for asset '%s'. Emissions returned as 0.0.",
asset.name,
)
return 0.0
# Calculate time factor for annualization
time_factor = SECONDS_PER_YEAR / duration if annualize else 1
# Calculate energy sum
energy_sum = sum(ts.values) * ts.time_step # Joules
# Calculate emissions
# The emission factor from adapter is in kg/J (already divided by 1e9)
# We multiply directly by energy in Joules and time factor
# The result is in kg, so we need to convert to tons
emissions_kg = asset.emission_factor * energy_sum * time_factor # kg
return emissions_kg * KG_TO_TONS