Skip to content

Enrichment modules

SPRUCE generates its estimates by chaining EnrichmentModules. An EnrichmentModule is the unit of extension in SPRUCE. Each module reads columns from the CUR input row and/or from values set by earlier modules, then writes its results into a shared map. The pipeline materialises one output row per CUR row at the end, avoiding per-module row copies.

The enrichment modules are listed and configured in a configuration file, one per cloud provider and report format. If no configuration is specified, SPRUCE uses the bundled default for the active provider (e.g. default-config-aws.json for a native AWS CUR, or default-config-aws-focus.json / default-config-azure-focus.json for an AWS or Azure FOCUS report). See Configure the modules for instructions on how to modify the enrichment modules, and Write a module to add your own. The list of columns generated by the modules can be found in the SpruceColumn class.

Native and FOCUS report formats

A FOCUS export carries mostly the same values as the provider's native export under different column names (e.g. for Azure x_SkuMeterCategory instead of MeterCategory, for AWS x_Operation instead of line_item_operation and SkuMeter instead of line_item_usage_type). The provider modules therefore work for both formats: when the report format is FOCUS, they automatically rebind their input columns to the FOCUS names. Two AWS specificities: x_ServiceCode carries the CUR product code (line_item_product_code), and the columns with no FOCUS equivalent are derived from SkuMeter β€” the instance type from meters like BoxUsage:m5.large and the data transfer category from the meter suffixes (DataTransfer-Regional-Bytes, AWS-Out-Bytes, ...).

The pipeline at a glance

The modules run in sequence and fall into five stages. Later stages consume the columns written by earlier ones, which is why the order in the configuration file matters.

flowchart LR
    A[Billing row] --> B("<b>Metadata</b><br>RegionExtraction")
    B --> C("<b>Energy &amp; embodied</b><br>Storage, Networking,<br>Serverless, Accelerators,<br>Boavizta, EcoLogits")
    C --> D("<b>Factors</b><br>PWUE,<br>AverageCarbonIntensity")
    D --> E("<b>Impacts</b><br>OperationalEmissions,<br>Water")
    E --> F("<b>Normalisation</b><br>FOCUSColumns<br>(native formats only)")
    F --> G[Enriched row]
Module Providers Writes Based on
RegionExtraction AWS, Azure, FOCUS region β€”
Storage AWS, Azure operational_energy_kwh Cloud Carbon Footprint
Networking AWS, Azure operational_energy_kwh Boavizta coefficients
Serverless AWS operational_energy_kwh Tailpipe
Accelerators AWS operational_energy_kwh Cloud Carbon Footprint
Compute β€” Boavizta AWS, Azure operational_energy_kwh, embodied_emissions_co2eq_g, embodied_adp_sbeq_g BoaviztAPI
LLM inference β€” EcoLogits AWS operational_energy_kwh, embodied_emissions_co2eq_g EcoLogits
PWUE AWS, Azure power_usage_effectiveness, water_usage_effectiveness Provider-published data
AverageCarbonIntensity AWS, Azure carbon_intensity Ember
OperationalEmissions AWS, Azure operational_emissions_co2eq_g β€”
Water AWS, Azure water_cooling_l, water_electricity_production_l, water_consumption_stress_area_l WRI
FOCUSColumns AWS, Azure (native formats) FOCUS columns β€”

Stage 1 β€” Metadata extraction

RegionExtraction

Extracts the region information from the provider-specific input columns and stores it in a standard location, where all the following modules can find it. The FOCUS variant is provider-neutral: it reads the standard RegionId column.

Classes com.digitalpebble.spruce.modules.aws.RegionExtraction
com.digitalpebble.spruce.modules.azure.RegionExtraction
com.digitalpebble.spruce.modules.focus.RegionExtraction
Writes region

Stage 2 β€” Energy and embodied estimates

The modules in this stage estimate the energy used by a row of usage β€” and, for Boavizta and EcoLogits, the related embodied emissions. Each module handles a different kind of usage (storage, networking, compute, …), so they complement each other rather than overlap.

Storage

Estimates the energy used for storage by applying a flat coefficient per GB, following the approach of the Cloud Carbon Footprint project. Service-specific replication factors are applied. On Azure, managed disks are estimated from their provisioned capacity.

Classes com.digitalpebble.spruce.modules.ccf.aws.Storage
com.digitalpebble.spruce.modules.ccf.azure.Storage
Writes operational_energy_kwh

Configuration (in Wh per TB-hour):

Key Default Description
hdd_coefficient_tb_h 0.65 Energy per TB-hour for HDD storage
ssd_coefficient_tb_h 1.2 Energy per TB-hour for SSD storage

Networking

Estimates the energy used for networking in and out of data centres. The module distinguishes between three transfer types with separate coefficients, taken from the Boavizta Cloud Emissions Working Group.

In a CUR, the transfer type is read from the transfer_type product attribute of the AWSDataTransfer lines. A FOCUS report carries neither, so the category is derived from the SkuMeter suffixes instead (DataTransfer-Regional-Bytes and DataTransfer-xAZ-*-Bytes β†’ intra, AWS-*-Bytes and CloudFront-*-Bytes β†’ inter, DataTransfer-In/Out-Bytes β†’ external), which map one-to-one to the CUR transfer types.

Classes com.digitalpebble.spruce.modules.aws.Networking
com.digitalpebble.spruce.modules.azure.Networking
Writes operational_energy_kwh

Configuration β€” the network_coefficients_kwh_gb map (in kWh/GB):

Transfer type Key Default Description
Intra-region intra 0.001 Traffic within the same region
Inter-region inter 0.0015 Traffic between regions
External extra 0.059 Traffic to/from the internet (Inbound / Outbound)

Attributing networking emissions

The relevance and usefulness of attributing emissions for networking based on usage is subject for debate, as the energy use of networking is pretty constant independently of traffic. The consequences of reducing networking are probably negligible but since the approach in SPRUCE is attributional, we do the same for networking in order to be consistent.

Serverless

Estimates the energy for the memory and vCPU usage of serverless services like Fargate or EMR. The default coefficients are taken from the Tailpipe methodology.

Class com.digitalpebble.spruce.modules.aws.Serverless
Writes operational_energy_kwh

Configuration:

Key Default Description
memory_coefficient_kwh 0.0000598 kWh per GB of memory
arm_cpu_coefficient_kwh 0.00191015625 kWh per vCPU (ARM)
x86_cpu_coefficient_kwh 0.0088121875 kWh per vCPU (x86)

Accelerators

Estimates the energy used by accelerators (GPUs), following the approach of the Cloud Carbon Footprint project: the power draw is interpolated between the minimum and maximum wattage of the accelerator at an assumed utilisation rate.

Class com.digitalpebble.spruce.modules.ccf.aws.Accelerators
Writes operational_energy_kwh

Configuration:

Key Default Description
gpu_utilisation_percent 50 Assumed GPU utilisation rate

Compute β€” Boavizta

Estimates the final energy used for computation (e.g. EC2, OpenSearch, RDS on AWS; virtual machines on Azure), as well as the related embodied emissions and abiotic resource depletion, using the BoaviztAPI.

In a CUR, the instance type is read from product_instance_type; a FOCUS report does not carry it, so it is parsed from the SkuMeter column instead (e.g. EUW2-BoxUsage:t3.xlarge).

Each provider has two variants:

  • BoaviztAPI queries a running instance of the API (docker run -p 5000:5000 ghcr.io/boavizta/boaviztapi:latest). The address can be set with the address configuration key (default http://localhost:5000).
  • BoaviztAPIstatic reads the same information from a static file generated from the API and bundled with SPRUCE. No API instance is needed, which makes it simpler to use β€” this is the variant enabled in the default configurations.
Classes com.digitalpebble.spruce.modules.boavizta.aws.BoaviztAPI
com.digitalpebble.spruce.modules.boavizta.aws.BoaviztAPIstatic
com.digitalpebble.spruce.modules.boavizta.azure.BoaviztAPI
com.digitalpebble.spruce.modules.boavizta.azure.BoaviztAPIstatic
Writes operational_energy_kwh, embodied_emissions_co2eq_g, embodied_adp_sbeq_g
What is Abiotic Depletion Potential (ADP)?

From the BoaviztAPI documentation:

Abiotic Depletion Potential (ADP) is an environmental impact indicator. This category corresponds to mineral and resources used and is, in this sense, mainly influenced by the rate of resources extracted. The effect of this consumption on their depletion is estimated according to their availability stock at a global scale. This impact category is divided into two components: a material component and a fossil fuels component (we use a version of ADP which includes both). This impact is expressed in grams of antimony equivalent (gSbeq).

Source: sciencedirect

LLM inference β€” EcoLogits

Estimates the energy consumption and embodied emissions of LLM inference on AWS Bedrock, based on static per-model coefficients derived from the EcoLogits project. Like BoaviztAPIstatic, a static data file bundled in the JAR is loaded at initialisation time; the module then matches Bedrock CUR rows to per-model coefficients.

The module parses the line_item_usage_type field (format: {REGION}-{ModelKey}-{input|output}-tokens[-batch]) to extract both the model key and the token type, then normalises the token count from pricing_unit (handling real-world values such as 1K tokens or 1M tokens). Only output-token rows are scored β€” the EcoLogits methodology attributes ~all generation cost to the autoregressive output phase, so input-token rows are skipped.

Class com.digitalpebble.spruce.modules.ecologits.BedrockEcoLogits
Writes operational_energy_kwh, embodied_emissions_co2eq_g

Batch size assumption

EcoLogits hardcodes a batch size of B=64 concurrent requests. The resulting coefficients are a mid-batch estimate β€” they underestimate energy for low-traffic scenarios and overestimate it for high-throughput batch inference (e.g. Bedrock Batch mode). Making B dynamic requires provider telemetry not available in billing data.

Stage 3 β€” Efficiency and intensity factors

The modules in this stage do not estimate energy themselves; they attach the per-region factors that the impact modules in the next stage multiply the energy estimates by.

PWUE

Loads both Power Usage Effectiveness (PUE) and Water Usage Effectiveness (WUE) factors from a CSV resource file bundled per provider: aws-pue-wue.csv uses the 2024 data published by AWS, and azure-pue-wue.csv is sourced from Microsoft's data centre sustainability pages.

The lookup logic follows this priority:

  1. Exact region match (e.g. us-east-1)
  2. Regex pattern match (e.g. us-.+)
  3. Default configured value (fallback to 1.15 for PUE, null for WUE)
Class com.digitalpebble.spruce.modules.PWUE
Reads region
Writes power_usage_effectiveness, water_usage_effectiveness

Configuration:

Key Default Description
default 1.15 PUE used when a region matches neither an exact entry nor a pattern

AverageCarbonIntensity

Adds average carbon intensity factors derived from Ember's electricity data, distributed under the Creative Commons Attribution Licence (CC-BY-4.0). Values are keyed directly by cloud provider and region (e.g. aws:us-east-1). For regions in countries with sub-national data (currently the US and India), the carbon intensity is taken from the Ember value for the state hosting the data centre; otherwise the country-level Ember value is used.

The data is loaded from ember/ember_co2_intensity.csv, which is generated from cloud_regions.json β€” see the scripts under scripts/ and the dedicated README for how to refresh it.

Class com.digitalpebble.spruce.modules.ember.AverageCarbonIntensity
Reads region
Writes carbon_intensity

Stage 4 β€” Impacts

The modules in this stage combine the energy estimates from stage 2 with the factors from stage 3 to produce the final impact columns.

OperationalEmissions

Computes operational emissions from the energy usage, carbon intensity and PUE estimated by the preceding modules:

operational_emissions_co2eq_g =
    operational_energy_kwh Γ— carbon_intensity Γ— power_usage_effectiveness
    Γ— powerSupplyEfficiency Γ— powerTransmissionLosses

It accounts for two additional overheads:

  • Power Supply Efficiency: the power lost between the data centre mains electricity and the server (default 1.04).
  • Power Transmission Losses: the power lost between the power station and the data centre mains electricity (default 1.08).
Class com.digitalpebble.spruce.modules.OperationalEmissions
Reads operational_energy_kwh, carbon_intensity, power_usage_effectiveness
Writes operational_emissions_co2eq_g

Configuration:

Key Default Description
powerSupplyEfficiency 1.04 Losses between mains and server
powerTransmissionLosses 1.08 Losses between power station and data centre

Water

Estimates the water consumption associated with cloud usage, producing three columns:

  • water_cooling_l β€” the volume of water (in litres) used for data centre cooling. Computed as operational_energy_kwh Γ— power_usage_effectiveness Γ— WUE, where WUE is the ratio of litres of water consumed for cooling per kWh of IT energy, loaded per region by the PWUE module.

  • water_electricity_production_l β€” the volume of water (in litres) consumed during electricity generation to power the data centre. Computed as operational_energy_kwh Γ— power_usage_effectiveness Γ— WCF, where WCF (Water Consumption Factor) represents the litres of water consumed per kWh of electricity generated. The WCF values per electricity grid zone are sourced from the WRI methodology for calculating water use embedded in purchased electricity.

  • water_consumption_stress_area_l β€” the total water consumption (water_cooling_l + water_electricity_production_l) attributed to regions under high or extremely high water stress. This field is only populated when the electricity grid zone for the region has a baseline water stress category of 3 (High) or 4 (Extremely High) in the WRI Aqueduct 4.0 dataset; it is absent otherwise. The Aqueduct data is licensed through Creative Commons and has been extracted and mapped to cloud provider region codes.

Class com.digitalpebble.spruce.modules.Water
Reads operational_energy_kwh, power_usage_effectiveness, water_usage_effectiveness, region
Writes water_cooling_l, water_electricity_production_l, water_consumption_stress_area_l

Stage 5 β€” Output normalisation

FOCUSColumns

Bridges provider-native billing columns to provider-neutral FOCUS (FinOps Open Cost & Usage Specification) columns, so the reporting scripts and dashboard can consume enriched data with the same column names whatever the input format. Only the columns the reporting scripts and dashboard rely on are covered β€” this is not a full native-to-FOCUS conversion, and values are copied as-is (e.g. ChargeCategory carries the raw line_item_line_item_type / ChargeType vocabulary). Runs last in the pipeline, after region has been set by RegionExtraction.

These modules are only needed for the native formats: a FOCUS report already carries these columns on input, so the FOCUS pipelines do not include them.

For AWS CURs:

FOCUS column AWS source
BilledCost line_item_unblended_cost
ListCost pricing_public_on_demand_cost
RegionId region (normalised by RegionExtraction)
ServiceName product_servicecode
ChargeCategory line_item_line_item_type
SubAccountId line_item_usage_account_id
ChargePeriodStart line_item_usage_start_date
ChargePeriodEnd line_item_usage_end_date
Tags resource_tags, copied as a map of tag key/values as in AWS FOCUS exports
SkuMeter line_item_usage_type (e.g. EUW2-BoxUsage:t3.xlarge, from which reporting derives instance types)
x_ServiceCode line_item_product_code
x_Operation line_item_operation

For Azure cost details exports (columns that already carry a FOCUS-compatible name in the input, such as BillingCurrency and Tags, pass through unchanged β€” except that Tags, which EA exports truncate to a JSON fragment without the enclosing braces, is wrapped back into a valid JSON object when the report is loaded):

FOCUS column Azure source
BilledCost CostInBillingCurrency
RegionId region (normalised by RegionExtraction)
ServiceName MeterCategory
ChargeCategory ChargeType
SubAccountId SubscriptionId
ChargePeriodStart Date
ChargePeriodEnd Date + 1 day
Classes com.digitalpebble.spruce.modules.aws.FOCUSColumns
com.digitalpebble.spruce.modules.azure.FOCUSColumns
Reads region and the provider-native columns above
Writes BilledCost, ListCost, RegionId, ServiceName, ChargeCategory, SubAccountId, ChargePeriodStart, ChargePeriodEnd, Tags, SkuMeter, x_ServiceCode, x_Operation

Supporting data

Cloud region metadata

SPRUCE ships with cloud_regions.json, a single JSON file listing the AWS, GCP, and Azure cloud regions together with their location (country, metro area, latitude/longitude), service status, and number of availability zones. It is the canonical source for the region-to-location mapping used by the modules and resource files (e.g. AverageCarbonIntensity).

The file is produced in two steps by scripts under scripts/, see scripts/README.md for the full usage details.