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 & 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.RegionExtractioncom.digitalpebble.spruce.modules.azure.RegionExtractioncom.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.Storagecom.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.Networkingcom.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 theaddressconfiguration key (defaulthttp://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.BoaviztAPIcom.digitalpebble.spruce.modules.boavizta.aws.BoaviztAPIstaticcom.digitalpebble.spruce.modules.boavizta.azure.BoaviztAPIcom.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:
- Exact region match (e.g.
us-east-1) - Regex pattern match (e.g.
us-.+) - 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 asoperational_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 asoperational_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.FOCUSColumnscom.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.