Source code for breos.emissions

"""
Emissions module for CO2 savings calculations.

This module handles:
- Grid carbon intensity parameters per country (average and marginal)
- CO2 emissions avoided by PV production (total and self-consumed)
- Multi-year CO2 savings projections
"""

from dataclasses import dataclass
from typing import Dict, Optional

import numpy as np
import pandas as pd


[docs] @dataclass class EmissionsParams: """Parameters for CO2 emissions calculations. Either average or marginal grid carbon intensity must be provided. When a marginal intensity is given, it is used for avoided-emissions accounting (the more accurate signal for grid CO2 displacement); otherwise the average intensity is used. """ average_grid_carbon_intensity_gco2_kwh: Optional[float] = None marginal_grid_carbon_intensity_gco2_kwh: Optional[float] = None source: str = "" # Data source citation for average intensity marginal_source: str = "" # Data source citation for marginal intensity year: int = 2024 # Reference year for the data country: str = "" # Country name @property def average_intensity_gco2_kwh(self) -> float: if self.average_grid_carbon_intensity_gco2_kwh is not None: return float(self.average_grid_carbon_intensity_gco2_kwh) if self.marginal_grid_carbon_intensity_gco2_kwh is not None: return float(self.marginal_grid_carbon_intensity_gco2_kwh) raise ValueError("EmissionsParams requires an average or marginal grid carbon intensity") @property def avoided_intensity_gco2_kwh(self) -> float: if self.marginal_grid_carbon_intensity_gco2_kwh is not None: return float(self.marginal_grid_carbon_intensity_gco2_kwh) return self.average_intensity_gco2_kwh @property def avoided_intensity_type(self) -> str: return "marginal" if self.marginal_grid_carbon_intensity_gco2_kwh is not None else "average"
[docs] def calculate_co2_savings( total_pv_kwh: float, self_consumed_kwh: float, emissions_params: EmissionsParams, ) -> Dict[str, float]: """ Calculate CO2 emissions avoided by PV production. Args: total_pv_kwh: Total PV production in kWh self_consumed_kwh: Self-consumed PV in kWh (PV_Production - Sell_To_Grid) emissions_params: Emissions parameters with grid carbon intensity Returns: Dict with CO2 avoided metrics in kg and tonnes. """ ci = emissions_params.avoided_intensity_gco2_kwh co2_total_kg = total_pv_kwh * ci / 1000 co2_self_kg = self_consumed_kwh * ci / 1000 return { "CO2_Avoided_Total_kg": co2_total_kg, "CO2_Avoided_SelfConsumed_kg": co2_self_kg, "CO2_Avoided_Total_tCO2": co2_total_kg / 1000, "CO2_Avoided_SelfConsumed_tCO2": co2_self_kg / 1000, "Grid_Carbon_Intensity_gCO2_kWh": ci, "CO2_Avoided_Intensity_gCO2_kWh": ci, "CO2_Avoided_Intensity_Type": emissions_params.avoided_intensity_type, "Average_Grid_Carbon_Intensity_gCO2_kWh": emissions_params.average_intensity_gco2_kwh, "Marginal_Grid_Carbon_Intensity_gCO2_kWh": emissions_params.marginal_grid_carbon_intensity_gco2_kwh, }
[docs] def calculate_co2_projection( yearly_pv_kwh: np.ndarray, yearly_export_kwh: np.ndarray, emissions_params: EmissionsParams, ) -> pd.DataFrame: """ Calculate multi-year CO2 savings projection. Args: yearly_pv_kwh: Array of PV production per year (kWh) yearly_export_kwh: Array of grid export per year (kWh) emissions_params: Emissions parameters Returns: DataFrame with yearly and cumulative CO2 avoided columns. """ ci = emissions_params.avoided_intensity_gco2_kwh n_years = len(yearly_pv_kwh) yearly_self_consumed = yearly_pv_kwh - yearly_export_kwh co2_total = yearly_pv_kwh * ci / 1000 co2_self = yearly_self_consumed * ci / 1000 proj = pd.DataFrame( { "Year": range(1, n_years + 1), "CO2_Avoided_Total_kg": co2_total, "CO2_Avoided_SelfConsumed_kg": co2_self, "CO2_Avoided_Total_Cumulative_kg": np.cumsum(co2_total), "CO2_Avoided_SelfConsumed_Cumulative_kg": np.cumsum(co2_self), "Grid_CI_gCO2_kWh": ci, "CO2_Avoided_CI_gCO2_kWh": ci, "CO2_Avoided_CI_Type": emissions_params.avoided_intensity_type, "Average_Grid_CI_gCO2_kWh": emissions_params.average_intensity_gco2_kwh, "Marginal_Grid_CI_gCO2_kWh": emissions_params.marginal_grid_carbon_intensity_gco2_kwh, } ) return proj