Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions docs/src/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,27 +243,41 @@ factors that the impact modules in the next stage multiply the energy estimates
### 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`](https://github.com/DigitalPebble/spruce/blob/main/src/main/resources/aws-pue-wue.csv) uses the 2024 data
[published by AWS](https://sustainability.aboutamazon.com/aws-wue-pue.csv), and
factors from a CSV resource file bundled per provider: [`aws-pue-wue.csv`](https://github.com/DigitalPebble/spruce/blob/main/src/main/resources/aws-pue-wue.csv) carries the 2022–2025 figures
[published by AWS](https://sustainability.aboutamazon.com/products-services/aws-cloud), and
[`azure-pue-wue.csv`](https://github.com/DigitalPebble/spruce/blob/main/src/main/resources/azure-pue-wue.csv) is sourced from [Microsoft's data centre sustainability pages](https://datacenters.microsoft.com/sustainability/efficiency/).

These factors are published per year and move noticeably from one year to the next, so they
are keyed by region **and** year rather than applied as a blanket value. The year is that of
the line item's usage date, read from whichever of `ChargePeriodStart`,
`line_item_usage_start_date`, `Date` or `BILLING_PERIOD` the report carries.

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)
2. Regex pattern match (e.g. `eu-.+`), i.e. the geography-level average
3. The provider-wide `GLOBAL` average, where the CSV has one
4. Default configured value (fallback to 1.15 for PUE, null for WUE)

Within a tier the entry for the usage year is used; when that year is not covered — a
region AWS started reporting on recently, a WUE only published from 2024 onwards, or a row
with no usable date — the closest year available for that region is used instead.

!!! note "Azure figures are not dated"
Microsoft does not break its PUE and WUE down by year, so the rows in
`azure-pue-wue.csv` leave the year empty and apply to every year.

| | |
|---|---|
| **Class** | `com.digitalpebble.spruce.modules.PWUE` |
| **Reads** | `region` |
| **Reads** | `region`, the usage date of the line item |
| **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 |
| `default` | 1.15 | PUE used when a region matches no entry at any tier, global average included |

### AverageCarbonIntensity

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/digitalpebble/spruce/CURColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class CURColumn extends RowColumn {
public static CURColumn LINE_ITEM_PRODUCT_CODE = new CURColumn("line_item_product_code", StringType);
public static CURColumn LINE_ITEM_TYPE = new CURColumn("line_item_line_item_type", StringType);
public static CURColumn LINE_ITEM_USAGE_TYPE = new CURColumn("line_item_usage_type", StringType);
public static CURColumn LINE_ITEM_USAGE_START_DATE = new CURColumn("line_item_usage_start_date", StringType);
public static CURColumn LINE_ITEM_USAGE_END_DATE = new CURColumn("line_item_usage_end_date", StringType);
public static CURColumn PRICING_UNIT= new CURColumn("pricing_unit", StringType);
public static CURColumn PRODUCT = new CURColumn("product", MapType.apply(StringType,StringType));
public static CURColumn PRODUCT_INSTANCE_TYPE = new CURColumn("product_instance_type", StringType);
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/com/digitalpebble/spruce/RowColumn.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import org.apache.spark.sql.types.DataType;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Abstract base class for native column types that work with Spark Row objects.
Expand Down Expand Up @@ -75,6 +79,44 @@ public LocalDate getDate(Row r) {
return null;
}

/** Matches a four-digit year not embedded in a longer number, e.g. in 2025-01-01T00:00:00Z,
* 01/15/2025 or the yyyy-MM of a billing period. */
private static final Pattern YEAR = Pattern.compile("(?<!\\d)(?:19|20)\\d{2}(?!\\d)");

/**
* Returns the year of the date or timestamp held by this column in the given row, or null
* when the column is absent from the schema, holds null, or cannot be read as a date.
* Covers the representations the reports use: Spark timestamps and dates in Parquet, and
* strings in CSV exports (ISO instants, ISO or US-formatted dates, and yyyy-MM periods).
*/
public Integer getYear(Row r) {
int index = resolveIndex(r, true);
if (index == -1) {
return null;
}
Object value = r.get(index);
if (value == null) {
return null;
}
if (value instanceof java.sql.Timestamp timestamp) {
return timestamp.toLocalDateTime().getYear();
}
if (value instanceof java.time.Instant instant) {
return instant.atZone(ZoneOffset.UTC).getYear();
}
if (value instanceof java.sql.Date date) {
return date.toLocalDate().getYear();
}
if (value instanceof LocalDate date) {
return date.getYear();
}
if (value instanceof LocalDateTime dateTime) {
return dateTime.getYear();
}
Matcher matcher = YEAR.matcher(value.toString());
return matcher.find() ? Integer.valueOf(matcher.group()) : null;
}

/** Returns true if the value for this column is null in the given row. */
public boolean isNullAt(Row r) {
return r.isNullAt(resolveIndex(r));
Expand Down
Loading