Skip to content

Commit a82e7ee

Browse files
adriancoclaude
andcommitted
Add annual-cloud-data-update skill for yearly metadata refresh
A Claude Code skill that documents and drives the annual update of Cloud_Region_Metadata.csv when AWS, Google, Azure and Oracle publish their sustainability data. Three-phase workflow: - Phase A: extract each vendor's metrics AND their verbatim metric definitions into metrics/<year>/<vendor>-<year>-metrics.md (including downloading AWS's ~32 per-region fact sheets and Microsoft's detailed Environmental Data Fact Sheet). - Phase B: merge only metrics whose definition fits the schema column, additively, preserving CRLF and never modifying published years. - Phase C: write metrics/<year>/gaps-<year>.md, a cross-vendor alignment report on what each vendor should change for comparable metrics. Captures the hard-won details: per-vendor sources and region-code maps; the location-vs-market CFE rule (don't populate the global 100% claim); AWS's per-grid renewable list; fiscal-vs-calendar timescale (Azure/Oracle fiscal years end mid-calendar-year); CRLF handling; the awk/quoted-comma trap; and the additive-only verification. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 1511730 commit a82e7ee

7 files changed

Lines changed: 612 additions & 0 deletions

File tree

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
---
2+
name: annual-cloud-data-update
3+
description: >-
4+
Update the real-time-cloud Cloud_Region_Metadata.csv with a new year of cloud-provider
5+
sustainability data (PUE, WUE, carbon-free / renewable energy %, grid carbon intensity)
6+
when AWS, Google, Microsoft Azure or Oracle publish their annual reports. Use this whenever
7+
the user wants to add or refresh a year of cloud region metadata, extract PUE/WUE/CFE from a
8+
provider sustainability report, reconcile a provider's disclosures into this dataset, or open
9+
the annual update PRs. Providers publish on a rolling schedule (Azure ~May, Google ~June,
10+
Amazon ~July, Oracle ~Dec), so this is typically run several times a year as each vendor lands.
11+
---
12+
13+
# Annual cloud region metadata update
14+
15+
This repo (`Green-Software-Foundation/real-time-cloud`) normalises annual sustainability metadata
16+
from the major cloud providers into one CSV, `Cloud_Region_Metadata.csv`, on the schema described
17+
in `Cloud_Region_Metadata_specification.md`. Each year the providers publish a new year of data and
18+
this file gets extended. This skill captures how to do that cleanly, because the work is fiddly and
19+
several mistakes are easy to make and hard to spot.
20+
21+
Read this file, then read the per-provider reference for whichever vendor(s) you're updating:
22+
`references/aws.md`, `references/google.md`, `references/azure.md`, `references/oracle.md`. The two
23+
output docs have templates: `references/metrics-doc-template.md` and `references/gaps-report-template.md`.
24+
25+
## The two rules that matter most
26+
27+
**1. Additive only.** Never modify or delete a row for a year that's already published. Providers
28+
sometimes *restate* older numbers or *blank* a value in a later snapshot — do not chase those into
29+
the historical rows. A correct update has `removed: 0` in the diff check below. If the user asks to
30+
fix an older year, do it as an explicit, separate decision, not as a side effect.
31+
32+
**2. Per-region physical data only — not the global market claim.** Most providers claim "100%
33+
renewable/carbon-free" on a company-wide, market-based basis that nets purchases across grids that
34+
aren't electrically connected. That claim is **not useful per-region** and must not populate the
35+
carbon-free-energy columns. Specifically:
36+
- `provider-cfe-hourly` / `provider-cfe-annual` are for *location-based, per-region* carbon-free
37+
energy. Only fill them with a genuine per-region figure (e.g. Google's hourly CFE, Oracle's
38+
per-region renewable %). Do **not** set a uniform `1.0` because the provider claims global 100%.
39+
- `provider-carbon-intensity-market-annual` is the dedicated market column. It's acceptable to set
40+
it to `0` **only** where the provider publishes a genuine *per-region* 100% list (AWS matches
41+
energy within specific grids and names those regions). Carry that per-region, never as a uniform
42+
value.
43+
44+
When in doubt about whether a figure is per-region-physical or a global-market claim, leave it blank
45+
and flag it for the user — a blank is honest, an invented number is not.
46+
47+
## Workflow: three phases
48+
49+
The work runs in three phases — **extract → merge → gaps** — because the value of this project isn't
50+
just the merged table, it's making the providers' metrics *comparable*. If you jump straight to the
51+
CSV you lose the definitions, and the definitions are where the incomparability hides (one vendor's
52+
"PUE" is a 12-month rolling average across 88% of sites; another's is Jan–Dec for operated sites; a
53+
"100% renewable" claim can be global-market or per-grid). So capture the definitions first, merge only
54+
what genuinely fits the schema, and write up the gaps.
55+
56+
Outputs land in `metrics/<year>/`: one `<vendor>-<year>-metrics.md` per provider, and one shared
57+
`gaps-<year>.md`.
58+
59+
### Phase A — Extract data *and definitions* into `<vendor>-<year>-metrics.md`
60+
For each vendor, read the source report(s) (see the per-provider reference for exact URLs) and produce
61+
a standalone Markdown doc that a reader could use *without* the original PDF. For every metric capture:
62+
- **Metric** and the **value(s)** per region/scope, with units.
63+
- **The vendor's own definition, quoted** (e.g. AWS: PUE/WUE "for data centers operated by AWS between
64+
January 1 and December 31 of each year"). Quote it verbatim — the definition is the point.
65+
- **Reporting period — exact dates, not just a year label**: AWS and Google report calendar year
66+
(Jan–Dec); **Azure (FY Jul–Jun) and Oracle (FY Jun–May) report fiscal years that end mid-calendar-year**,
67+
so their "2025" ends ~mid-2025 and is a different window than a calendar-2025 figure. Also the
68+
boundary (operated vs owned+leased, % of sites covered, rolling-12-month vs annual). Record the exact
69+
period per metric — sometimes different tables in one report use different bases.
70+
- **Method**: location-based vs market-based; hourly vs annual; per-region vs macro/fleet-wide.
71+
- Anything that has **no home in the RTC schema** (e.g. Oracle's tCO₂e/$USD, AWS embodied carbon) —
72+
record it so the gaps report can note it.
73+
74+
Follow the standard `metrics-doc` template in `references/metrics-doc-template.md`. This doc is the
75+
input to both later phases; get it right and the merge is mechanical.
76+
77+
### Phase B — Merge conforming metrics into the metadata table
78+
Only metrics whose definition genuinely matches the schema column's intended meaning get merged (see
79+
`Cloud_Region_Metadata_specification.md` for what each column means, and rule 2 above for the
80+
CFE/market distinction). If a vendor's metric almost-but-not-quite fits, prefer leaving the cell blank
81+
and recording the mismatch in the gaps report over forcing a number in. The mechanics (build script,
82+
carry-forward, verify) are steps 1–5 below.
83+
84+
### Phase C — Write `gaps-<year>.md`
85+
A cross-vendor alignment report: for each metric in the schema, tabulate how each vendor defines and
86+
reports it, then call out what each vendor should change to make it comparable. This directly serves
87+
the project's mission of lobbying providers toward a common definition. See
88+
`references/gaps-report-template.md` for the structure and the recurring gaps to check every year
89+
(fiscal-vs-calendar year, PUE boundary/coverage, WUE availability, location-vs-market CFE, per-region
90+
vs macro/fleet reporting, missing per-region grid data).
91+
92+
---
93+
94+
## Merge mechanics (Phase B, step by step)
95+
96+
### 1. Set up a clean working base
97+
The authoritative state is `upstream/main` (GSF). Local `main` and the fork's `Dev` drift behind it.
98+
```
99+
git fetch upstream && git fetch origin
100+
git checkout main && git merge --ff-only upstream/main # if local main is behind and not ahead
101+
git checkout -b <year>-<provider>-update upstream/Dev # PRs target Dev; Dev == main data-wise
102+
```
103+
Confirm `Dev` and `main` hold identical data files before branching off `Dev` (they usually do; the
104+
few `main`-only commits are release merges / issue templates). One provider per branch/PR keeps
105+
review clean, unless the user wants them combined.
106+
107+
### 2. Find the source data
108+
Each provider has a tracking issue where the report link (and sometimes an extracted table) is
109+
posted — check open issues labelled `action item` (e.g. AWS #158, Google #159, Azure #161, Oracle
110+
#86). The report itself is the source of truth; a pasted table in an issue is a convenience. See the
111+
per-provider reference for exact URLs, which page/table to read, and the region-name→region-code
112+
mapping.
113+
114+
### 3. Build the new rows
115+
Prefer a small, explicit, reviewable Python script that encodes the source table and writes rows,
116+
over the older scraper scripts in `code/` (`aws-data-update.py` scrapes a popup and has historically
117+
mislabelled columns; trust the report/issue table instead). `gcp-data-update.py` pulling from the
118+
`region-carbon-info` repo is reliable for Google's CFE/grid data.
119+
120+
Carry forward slowly-changing metadata (`cfe-region`, `em-zone-id`, `wt-region-id`, `location`,
121+
`geolocation`, and grid carbon intensity) from the region's most recent prior year — no new
122+
Electricity Maps / WattTime lookup happens here. Only populate PUE/WUE/CFE for the new year where
123+
the provider actually disclosed it.
124+
125+
**New regions appear every year** (each provider launches several). A region with disclosed data but
126+
no prior row has nothing to carry forward — add it fresh rather than skipping it: **geocode a
127+
city-level geolocation** (`geopy` Nominatim on the city name, rounded to 4 dp — the dataset uses
128+
city-level, not exact datacenter coordinates), set `cfe-region`/`em-zone-id` from the country/grid
129+
(the Electricity Maps zone), fill the disclosed PUE/WUE/CFE, and leave `wt-region-id` + grid carbon
130+
intensity blank with a note that they need a WattTime / Electricity Maps lookup. Adding the region now
131+
with partial metadata is better than dropping it — the grid columns can be filled when the lookup runs.
132+
133+
**Water (`total-water-input`).** Some providers report a per-region annual water **withdrawal** in
134+
litres (AWS does, on each fact sheet). That maps to `total-water-input`. Watch the reporting boundary:
135+
where a provider reports by **country** but has several regions there, allocate/interpolate — if the
136+
regions are reported separately (AWS India: Mumbai/Hyderabad) use each; if combined (AWS Japan =
137+
Tokyo+Osaka together) allocate to the primary region and leave the other blank (or split on a stated
138+
basis), and document the choice. Convert gallons to litres where needed.
139+
140+
**Grid data (Electricity Maps / WattTime).** `em-zone-id`/`wt-region-id` are the lookup keys; the actual
141+
`grid-carbon-intensity-*` values come from those services. Electricity Maps publishes yearly zone data
142+
but via an **account-gated free portal** (`app.electricitymaps.com/datasets`, ~5 downloads) — not a
143+
fetchable URL; WattTime marginal data is **API-gated** (credentials). So for a brand-new region set the
144+
`em-zone-id` and leave grid carbon blank, and flag it for whoever has portal/API access to fill (the
145+
project has done this before — see issue #86). Don't block the row on it.
146+
147+
**Critical file-format details** (getting these wrong makes the diff look enormous):
148+
- The file is **CRLF**. Always write with `df.to_csv(path, index=False, lineterminator="\r\n")`.
149+
- Read with `dtype=str` so existing numeric strings aren't reformatted.
150+
- Sort `year` descending, then provider, then region (matches the repo convention).
151+
- The `geolocation` field contains a comma inside quotes, so **never parse this CSV with `awk -F,`**
152+
— it mis-splits every subsequent column. Use Python's `csv` module or pandas.
153+
154+
### 4. Verify before committing
155+
```
156+
python3 -c "import csv;r=list(csv.reader(open('Cloud_Region_Metadata.csv')));n=len(r[0]);\
157+
print('cols',n,'bad',[i for i,x in enumerate(r) if len(x)!=n][:5])" # expect 24 cols, no bad rows
158+
grep -c $'\r' Cloud_Region_Metadata.csv # CRLF preserved on every line
159+
diff <(git show upstream/Dev:Cloud_Region_Metadata.csv|sort) <(sort Cloud_Region_Metadata.csv)|grep -c '^<' # MUST be 0 (additive)
160+
```
161+
Then spot-check a handful of rows against the source report (right PUE in the PUE column, right WUE
162+
in WUE, CFE where expected, market column per the rules above).
163+
164+
### 5. Regenerate the estimate and update the README
165+
The published estimate table is the **complete best-guess** current-year table — every region gets a
166+
row and every tracked metric is filled (trended where the region has history, otherwise a regional
167+
best guess). Generate it with:
168+
```
169+
python3 code/complete_estimate.py Cloud_Region_Metadata.csv <next-year> # e.g. 2026
170+
```
171+
Fill rules that matter: **grid carbon intensity** is a physical grid property, so fill it from the same
172+
`em-zone-id` / continent (across providers is fine — it's the same grid). **Provider-specific metrics**
173+
(PUE, WUE, CFE %, market carbon, water) fill **only from the same provider's** regional data — **never
174+
estimate one provider's metric from another's**; if a provider doesn't report a metric anywhere (AWS/Azure
175+
CFE %, Google/Azure water, Google WUE), leave it blank. The never-reported EU-EED fields
176+
(`total-ICT-energy-consumption-annual`, `renewable-energy-*`) also stay blank. **Keep the reported table (`Cloud_Region_Metadata.csv`) clean — never fill best-guesses
177+
into it; the estimate table is the only place guesses belong.** (`estimate_current_region_metadata.py`
178+
is the older trend-only estimator, kept for reference/testing; it leaves gaps blank.) Update the
179+
"Cloud Providers" paragraph in `README.md` to state the latest real year and the projected year.
180+
181+
### 6. Open the PR and update the tracking issue
182+
Base the PR on `Dev`. Include the CSV/estimate/README changes **and** the phase-A `metrics/<year>/`
183+
docs and the phase-C `gaps-<year>.md` — they're deliverables, and they're the evidence for every
184+
merge decision and caveat. In the body, state the sources (with links), the per-provider column
185+
mapping, and every caveat/approximation. Comment on the provider's tracking issue with what landed and
186+
what's still missing. Commit trailer: `Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>`;
187+
PR-body trailer: `🤖 Generated with [Claude Code](https://claude.com/claude-code)`.
188+
189+
### 7. Archive the source documents
190+
Providers periodically move or delete these files (the AWS popup, gstatic-hosted Google reports and
191+
Oracle's data sheets have all changed URLs). Save the PDFs you used to `sources/<data-year>/<provider>/`
192+
with a `sources/README.md` manifest (document each file's source URL, retrieval date, and a **gaps table**
193+
for documents still needed — e.g. Oracle's CY report, Electricity Maps/WattTime data, prior-year fact
194+
sheets). Download prior years too where the URLs are still live. This is bulky (~180 MB/year of PDFs);
195+
keep it on its own branch/PR and flag **Git LFS or an external release-asset store** vs a plain commit so
196+
the WG decides before merging binaries into history.
197+
198+
## Tooling notes
199+
- PDFs: `pdfplumber` (reports) and `openpyxl` (Google-Sheet exports of working data). A working
200+
`venv` may have a Python-version/site-packages mismatch — if `import` fails, run the system
201+
`python3` with `PYTHONPATH=code/venv/lib/python3.XX/site-packages` pointing at wherever the
202+
packages actually installed.
203+
- Google Sheets shared by the user export as CSV/XLSX via
204+
`https://docs.google.com/spreadsheets/d/<ID>/export?format=xlsx`.
205+
- The scratchpad dir (per session) is the place for these one-off builder scripts, not the repo.
206+
207+
## Outputs of a full run
208+
A complete update produces, and the PR(s) should include: new **reported** rows in
209+
`Cloud_Region_Metadata.csv` (clean, real data only); the regenerated **complete estimate**
210+
`Cloud_Region_Metadata_estimate.csv` (best-guess, every cell filled); the phase-A
211+
`metrics/<year>/<vendor>-<year>-metrics.md` docs and phase-C `metrics/<year>/gaps-<year>.md`; a README
212+
update; and the `sources/<year>/` archive (own branch). Reported table clean, estimate table filled —
213+
never mix the two.
214+
215+
## What each provider gives you (summary — details in the references)
216+
| Provider | Per-region disclosed | Notes |
217+
|----------|----------------------|-------|
218+
| AWS | PUE, WUE, water withdrawal (per-region); per-grid 100%-renewable list | `references/aws.md`**~32 per-region PDF fact sheets** to download + process individually; friendly-name→code map; market=0 only for listed regions; regional-average PUE for newer regions |
219+
| Google | hourly CFE + grid carbon (region-carbon-info repo); per-datacenter PUE (report PDF) | `references/google.md` — annual CFE left blank; don't use the 100% claim |
220+
| Azure | only macro-regional (Americas/APAC/EMEA), fiscal-year PUE/WUE; detailed data fact sheet PDF for definitions | `references/azure.md` — map by continent; no per-region since 2022 |
221+
| Oracle | renewable % per region; PUE (CY report); grid via Electricity Maps | `references/oracle.md` — key→identifier map; gov regions inherit co-located grid |
222+
| Others | IBM/Alibaba/OVHcloud publish only fleet-wide PUE — not usable per-region | skip unless per-region data appears; still note them in the gaps report |

0 commit comments

Comments
 (0)