feat: carbon-aware codecarbon wait (forecast + greenest-window selection) - #1363
Open
davidberenstein1957 wants to merge 6 commits into
Open
feat: carbon-aware codecarbon wait (forecast + greenest-window selection)#1363davidberenstein1957 wants to merge 6 commits into
codecarbon wait (forecast + greenest-window selection)#1363davidberenstein1957 wants to merge 6 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## feat/intensity-providers #1363 +/- ##
============================================================
+ Coverage 91.54% 91.77% +0.22%
============================================================
Files 49 51 +2
Lines 5122 5253 +131
============================================================
+ Hits 4689 4821 +132
+ Misses 433 432 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
davidberenstein1957
force-pushed
the
feat/carbon-aware-scheduling
branch
from
August 13, 2026 05:28
a71bcb1 to
e7022e6
Compare
davidberenstein1957
changed the base branch from
master
to
feat/intensity-providers
August 13, 2026 05:28
davidberenstein1957
marked this pull request as ready for review
August 13, 2026 05:31
Fetch an Electricity Maps carbon intensity forecast, pick the window with the lowest mean intensity that still meets the deadline, and either report it (--dry-run) or sleep until it and delegate to run_and_monitor. Advisory/blocking only: no EmissionsData schema change, no decorator, and no static fallback profile. Without a token, get_forecast returns None and the job runs immediately -- a job is never blocked on a missing credential. get_forecast should become a method on the provider protocol once pluggable intensity providers land. Refs #1356 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add a CLI reference section for `codecarbon wait` covering every flag and its default, the forecast requirements, and the run-now degradation when no forecast is available, plus one cross-link from the CLI tutorial. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`codecarbon wait` had its own HTTP path to Electricity Maps. It now goes through `electricitymaps_api.request`, so the token lookup, the request plumbing and the exponential failure cooldown are shared with the current-intensity path: a failing API is backed off once, process-wide. The forecast response is deliberately not put in the intensity cache. That cache exists for a value refetched on every measurement tick with a 300 s TTL; a forecast is fetched once per `wait` invocation and has a completely different useful lifetime. `get_forecast` still never raises: a cooldown is just one more reason to return None and run the job now. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`--deadline` is documented as the maximum delay before the job must start, but `best_window` rejected any window finishing after it, so `--deadline 12h --duration 2h` only searched 10h of start times. The documented semantics win: the deadline now bounds the start, the horizon request is widened to deadline + duration, and the docs and tests say so. Also stop stripping every argument equal to "wait" (it mangled `codecarbon wait -- make wait`; only a leading one is ours), and compare `--threshold` against the live carbon intensity instead of the first forecast point. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
davidberenstein1957
force-pushed
the
feat/carbon-aware-scheduling
branch
from
August 16, 2026 08:46
1ec74c6 to
ca960ea
Compare
`find_green_window` fetched the forecast and then asked /latest for the current intensity, a second HTTP call whose value only fed a "saves ~X%" line and the --threshold short-circuit. The forecast's first point is that same period, so use it and drop the call, the fallback and the try/except with it. Add --finish-by as the complement to --deadline: --deadline bounds the start, --finish-by bounds the end and is what most people mean. It is a subtraction, not a second search path. The Electricity Maps request extraction this branch used to carry now lives in its base branch (#1358) where it belongs, so `clear_cooldown` is gone: request() clears its own location's cooldown on a usable response. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
davidberenstein1957
force-pushed
the
feat/carbon-aware-scheduling
branch
from
August 16, 2026 08:47
ca960ea to
85df4b7
Compare
Drop the scheduler and forecast aphorisms, keep the Ctrl+C and no-forecast behaviour as plain statements, and remove the em-dashes the CLI reference does not use. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A deliberately narrow first slice of carbon-aware scheduling (#1356).
What is in it
codecarbon/core/intensity_forecast.py—IntensityPoint/Forecastdataclasses,get_forecast()against the Electricity Maps/carbon-intensity/forecastendpoint (going through feat: cache and back off Electricity Maps carbon intensity #1358's sharedrequest(), so a failing location backs off once for the whole process rather than once per caller), andbest_window(), a pure sliding-mean window search with no I/O.codecarbon/cli/wait.py+ awaitcommand incodecarbon/cli/main.py:--deadlineis the latest acceptable start, so--deadline 12h --duration 2hmay leave the job running 14 hours from now.--finish-byis the complement most people actually mean: it bounds the end, and is implemented asfinish_by - durationfeeding the same search. A--finish-byshorter than--durationis rejected.One API call.
waitfetches the forecast and nothing else. The "now" figure printed, and the value--thresholdcompares against, is the first forecast point — the current period as the forecast sees it. An earlier revision also hit/carbon-intensity/latestfor a live reading, which was a second network round trip in service of a percentage.The blocking form delegates to the existing
run_and_monitor, so measurement, CSV output and exit-code propagation are unchanged. The tracker only starts after the sleep, so a waiting process holds no lock. Ctrl-C during the wait starts the job immediately rather than aborting.get_forecastnever raises: no token, a non-200, a malformed payload or an empty forecast all returnNone, and every caller degrades to running now. A job is never blocked on a missing credential.What is deliberately not in it
@carbon_awaredecorator /wait_for_green_windowcontext manager. The CLI is the smaller surface and validates whether anyone wants the sleeping behaviour before we own a second one.deferred_seconds/avoided_emissionsonEmissionsData. Worth having, but they should ship with a proven blocking path — two always-zero CSV columns are a schema change for no reader.What is blocked on the intensity-provider work
Electricity Maps is currently the only source that can serve a forecast, and only for users holding a paid token. That caps how much of the audience this feature can reach, and it is why this is a narrow slice rather than the full design in #1356.
get_forecastis written to be absorbed: once pluggable intensity providers land it should become an optionalforecast()method on the provider protocol rather than a second HTTP client, and the HTTP lives in one place (#1358'srequest()) so that move is mechanical.Tests
tests/test_intensity_forecast.py(14) andtests/cli/test_wait.py(22), all passing, no network — HTTP is stubbed withresponsesas intests/test_electricitymaps_api.py, and geolocation, config andtime.sleepare monkeypatched in the CLI tests. Coverage includes payload parsing, lat/lon vscountryCodeselection, naive-vs-aware timestamps, horizon truncation, every failure mode returningNone, the shared cooldown skipping the request, the window search (trough, flat, monotonic, deadline shorter than duration, duration longer than horizon),--finish-bybounding the end and its rejection when shorter than--duration, the duration parser, the threshold short-circuit taking no second API call, and delegation torun_and_monitorwith the residual command.Docs: a
codecarbon waitsection indocs/reference/cli.mdand a pointer fromdocs/tutorials/cli.md.Refs #1356
🤖 Generated with Claude Code