Calculating Carbon Footprint

This page documents how the Ada Carbon Monitoring system calculates carbon footprint (gCO2eq) from electricity usage (kWh).

Formula

Carbon Footprint (gCO2eq) = Electricity Usage (kWh) × Carbon Intensity (gCO2/kWh)

Carbon Intensity

Carbon intensity measures the carbon dioxide emissions per unit of electricity generated. It varies based on:

  • Time of day (lower at night, higher during peak demand)
  • Weather (wind/solar generation vs fossil fuels)
  • Season (heating demand in winter)

UK Grid Carbon Intensity API

We use the UK Carbon Intensity API for real-time data:

Endpoint: https://api.carbonintensity.org.uk/intensity

Coverage: Great Britain (England, Scotland, Wales)

Update frequency: Every 30 minutes

Intensity Index

Index Range (gCO2/kWh) Description
Very Low 0-50 High renewable generation
Low 50-100 Good renewable mix
Moderate 100-200 Mixed generation
High 200-300 Higher fossil fuel use
Very High 300+ Peak demand, low renewables

Example Values (UK 2026)

Time Typical Intensity Notes
3 AM 120 gCO2/kWh Low demand, wind generation
9 AM 200 gCO2/kWh Morning peak, gas plants online
2 PM 150 gCO2/kWh Solar contributing
6 PM 250 gCO2/kWh Evening peak demand

Implementation

CarbonCalculator

class CarbonCalculator:
    """Calculate carbon footprint from electricity usage."""

    def __init__(self, carbon_client: CarbonIntensityAPIClient):
        self.carbon_client = carbon_client

    def estimate_carbon_footprint(
        self,
        kwh: float,
        carbon_intensity_g_per_kwh: Optional[float] = None
    ) -> float:
        """
        Calculate carbon footprint in grams CO2 equivalent.

        Args:
            kwh: Electricity usage in kilowatt-hours
            carbon_intensity_g_per_kwh: Carbon intensity (fetches current if not provided)

        Returns:
            Carbon footprint in gCO2eq
        """
        if carbon_intensity_g_per_kwh is None:
            current = self.carbon_client.get_current_intensity()
            carbon_intensity_g_per_kwh = current["intensity"]

        return kwh * carbon_intensity_g_per_kwh

    def estimate_carbon_footprint_detailed(
        self,
        busy_cpu_seconds: float,
        idle_cpu_seconds: float,
        busy_power_w: float = 12.0,
        idle_power_w: float = 1.0,
        start_time: Optional[datetime] = None
    ) -> dict:
        """
        Calculate detailed carbon footprint with breakdown.

        Returns dict with electricity_kwh, carbon_gco2eq (busy/idle/total),
        and carbon_intensity.
        """
        # Calculate electricity
        busy_kwh = busy_power_w * busy_cpu_seconds / 3_600_000
        idle_kwh = idle_power_w * idle_cpu_seconds / 3_600_000
        total_kwh = busy_kwh + idle_kwh

        # Get carbon intensity
        if start_time:
            intensity = self.carbon_client.get_intensity_for_time(start_time)
        else:
            intensity = self.carbon_client.get_current_intensity()["intensity"]

        # Calculate carbon
        busy_gco2eq = busy_kwh * intensity
        idle_gco2eq = idle_kwh * intensity
        total_gco2eq = total_kwh * intensity

        return {
            "electricity_kwh": {
                "busy": busy_kwh,
                "idle": idle_kwh,
                "total": total_kwh
            },
            "carbon_gco2eq": {
                "busy": busy_gco2eq,
                "idle": idle_gco2eq,
                "total": total_gco2eq
            },
            "carbon_intensity_g_per_kwh": intensity
        }

Location: ada-carbon-monitoring-api/src/calculators/carbon_calculator.py

CarbonIntensityAPIClient

class CarbonIntensityAPIClient:
    """Client for UK Carbon Intensity API."""

    BASE_URL = "https://api.carbonintensity.org.uk"

    def get_current_intensity(self) -> dict:
        """Get current carbon intensity."""
        response = requests.get(f"{self.BASE_URL}/intensity")
        data = response.json()["data"][0]
        return {
            "intensity": data["intensity"]["actual"] or data["intensity"]["forecast"],
            "index": data["intensity"]["index"],
            "from": data["from"],
            "to": data["to"]
        }

    def get_forecast(self, hours: int = 24) -> dict:
        """Get carbon intensity forecast."""
        response = requests.get(f"{self.BASE_URL}/intensity/fw{hours}h")
        return {
            "forecasts": [
                {
                    "from_time": p["from"],
                    "to_time": p["to"],
                    "intensity_forecast": p["intensity"]["forecast"],
                    "intensity_index": p["intensity"]["index"]
                }
                for p in response.json()["data"]
            ]
        }

    def get_intensity_for_time(self, timestamp: datetime) -> float:
        """Get intensity for a specific time (averages two 30-min periods)."""
        # Rounds to nearest hour, queries both half-hour periods
        # Returns average of actual values
        ...

Location: ada-carbon-monitoring-api/src/clients/carbon_intensity_client.py

Example Calculations

Example 1: Low Carbon Time

Computing at 3 AM when intensity is low:

Metric Value
Electricity 0.1 kWh
Carbon intensity 100 gCO2/kWh
carbon = 0.1 × 100 = 10 gCO2eq

Example 2: Peak Demand Time

Same computation at 6 PM when intensity is high:

Metric Value
Electricity 0.1 kWh
Carbon intensity 250 gCO2/kWh
carbon = 0.1 × 250 = 25 gCO2eq

Result: 2.5× more carbon for the same work!

Example 3: Full Day Breakdown

An 8-hour job running through varying intensity:

Period kWh Intensity gCO2eq
9 AM - 12 PM 0.03 200 6.0
12 PM - 3 PM 0.03 150 4.5
3 PM - 6 PM 0.03 220 6.6
Total 0.09 190 avg 17.1

API Endpoint

POST /carbon/calculate

Request:

{
  "busy_cpu_seconds": 1000,
  "idle_cpu_seconds": 5000,
  "busy_power_w": 12.0,
  "idle_power_w": 1.0
}

Response:

{
  "electricity_kwh": {
    "busy": 0.00333,
    "idle": 0.00139,
    "total": 0.00472
  },
  "carbon_gco2eq": {
    "busy": 0.617,
    "idle": 0.257,
    "total": 0.874
  },
  "carbon_intensity_g_per_kwh": 185,
  "power_w": {
    "busy": 12.0,
    "idle": 1.0
  },
  "equivalencies": {
    "total_gco2eq": 0.874,
    "top_equivalencies": {...}
  }
}

Time Shifting Opportunity

The carbon intensity forecast enables time shifting - scheduling work during low-carbon periods:

forecast = carbon_client.get_forecast(hours=24)

# Find best 3-hour window
best_window = find_lowest_average_window(forecast, hours=3)
print(f"Best time to run: {best_window['start']} ({best_window['avg_intensity']} gCO2/kWh)")

This is visualized in the Carbon Intensity Forecast chart on the dashboard.

Carbon Equivalencies

After calculating carbon footprint, we convert to relatable equivalencies:

from src.calculators.carbon_equivalency import CarbonEquivalencyCalculator

calc = CarbonEquivalencyCalculator()
equivalencies = calc.get_top_equivalencies(total_gco2eq)

# Returns:
# {
#     "smartphone_charges": {"value": 0.11, "unit": "charges", "description": "..."},
#     "miles_driven": {"value": 0.002, "unit": "miles", "description": "..."},
#     ...
# }

→ See Frontend Components for the CarbonEquivalencies Svelte component

Data Quality Notes

Prometheus data before March 2025 returns 0 due to label changes. Only data from March 2025 onwards is used for calculations.

If the Carbon Intensity API is unavailable, the system uses a default intensity of 200 gCO2/kWh (UK average) and marks the data as estimated.