greenpick 0.1.8__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {greenpick-0.1.8/src/greenpick.egg-info → greenpick-0.2.0}/PKG-INFO +25 -18
- {greenpick-0.1.8 → greenpick-0.2.0}/README.md +24 -17
- {greenpick-0.1.8 → greenpick-0.2.0}/pyproject.toml +1 -1
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/__init__.py +1 -1
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/__init__.py +4 -2
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/auto_repository.py +2 -2
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/eia_repository.py +2 -2
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/electricitymaps_repository.py +2 -2
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/entsoe_repository.py +2 -2
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/static_data.py +10 -0
- greenpick-0.2.0/src/greenpick/data/typical.json +3692 -0
- greenpick-0.2.0/src/greenpick/data/typical_repository.py +56 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/models.py +1 -1
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/server.py +3 -3
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/data_hint.py +54 -31
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/forecast_service.py +10 -1
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/scheduling_service.py +54 -12
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/training_service.py +11 -6
- {greenpick-0.1.8 → greenpick-0.2.0/src/greenpick.egg-info}/PKG-INFO +25 -18
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick.egg-info/SOURCES.txt +2 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/tests/test_services.py +1546 -1451
- {greenpick-0.1.8 → greenpick-0.2.0}/LICENSE +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/setup.cfg +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/__main__.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/config.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/_net.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/emission_factors.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/gpu_power.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/inference_energy.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/instance_families.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/mock_repository.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/network.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/power.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/pricing.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/regions.json +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/data/repository.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/__init__.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/carbon_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/cost_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/inference_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/instance_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/latency_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/locality_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/metric_scaffold_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/recommender.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/residency_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/rightsizing_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick/services/serving_service.py +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick.egg-info/dependency_links.txt +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick.egg-info/entry_points.txt +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick.egg-info/requires.txt +0 -0
- {greenpick-0.1.8 → greenpick-0.2.0}/src/greenpick.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: greenpick
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Design-time sustainability co-pilot (MCP server): greener, cheaper cloud infrastructure decisions with carbon + cost + latency tradeoffs.
|
|
5
5
|
Author-email: "Data Science Advisers, LLC" <info@datascienceadvisers.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -55,7 +55,8 @@ seconds. (It responds when you ask; auto-nudging on infra-code changes is on the
|
|
|
55
55
|
> **Status: working — 15 tools.** Live carbon for US (EIA-930) and EU (ENTSO-E), cross-cloud cost
|
|
56
56
|
> parity (Vantage-refreshed), per-axis R/Y/G ratings, carbon-aware scheduling + day-ahead forecast,
|
|
57
57
|
> GPU-training **and LLM-inference** carbon, right-sizing, serving-pattern and data-locality/residency
|
|
58
|
-
> checks — all shipped.
|
|
58
|
+
> checks — all shipped. Offline or without API keys, falls back to clearly-labelled *typical* values:
|
|
59
|
+
> this month's average for each grid, from a year of real data.
|
|
59
60
|
|
|
60
61
|
<p align="center">
|
|
61
62
|
<img src="https://raw.githubusercontent.com/bithug01/greenpick/master/docs/img/region-carbon-30x.svg" alt="Same H100 training run, ~30× the carbon depending only on region (Stockholm vs Sydney)" width="560" />
|
|
@@ -74,7 +75,7 @@ tradeoff) while you're still in the editor, before you commit.*
|
|
|
74
75
|
- ⚡ **GPU training & LLM inference carbon** — estimate a run's footprint and the greener *where* + *when*; the size/placement choices that actually move the number.
|
|
75
76
|
- 🌍 **Greener region picks** — ranked by carbon, with the cost + latency tradeoff shown, not hidden.
|
|
76
77
|
- 🛡 **Backfire guards** — flags when a greener move loses to egress, or crosses a data-residency line.
|
|
77
|
-
- 🔌 **Live US + EU grid data**,
|
|
78
|
+
- 🔌 **Live US + EU grid data**, with a realistic fallback built from a year of real data — **no API keys required to start**.
|
|
78
79
|
|
|
79
80
|
## Contents
|
|
80
81
|
|
|
@@ -186,8 +187,12 @@ See `docs/user-questions-backlog.md`.
|
|
|
186
187
|
|
|
187
188
|
- **EIA-930** (US balancing authorities) and **ENTSO-E** (EU zones) — live grid carbon,
|
|
188
189
|
auto-routed by region. Both also drive `cleanest_hours_to_run` from recent hourly history.
|
|
189
|
-
- **
|
|
190
|
-
|
|
190
|
+
- **Typical** — the offline default and fallback: for each region, this calendar month's average
|
|
191
|
+
intensity and average hour-of-day profile (UTC), computed from 12 months of real hourly EIA-930 /
|
|
192
|
+
ENTSO-E data (`data/typical.json`, refreshed by `scripts/refresh_typical.py`). Labelled
|
|
193
|
+
`quality: "typical"` — a realistic norm, never presented as a current reading.
|
|
194
|
+
- **Mock** — a hand-set illustrative value, labelled `quality: "mock"`, used only for regions no free
|
|
195
|
+
source covers (e.g. Sydney).
|
|
191
196
|
- Cost: a **dated static pricing table** (`data/pricing.json`), refreshed from live Vantage
|
|
192
197
|
pricing via `scripts/refresh_pricing.py` using one matched method across all three clouds.
|
|
193
198
|
- Instance-family efficiency: directional perf-per-watt multipliers (`data/instance_families.json`).
|
|
@@ -195,10 +200,10 @@ See `docs/user-questions-backlog.md`.
|
|
|
195
200
|
|
|
196
201
|
Set `EIA_API_KEY` and `ENTSOE_TOKEN` (both free) for live carbon — see
|
|
197
202
|
[Turn on live data](#turn-on-live-data); `GREENPICK_CARBON_SOURCE` selects the source (`auto` routes
|
|
198
|
-
US→EIA, EU→ENTSO-E,
|
|
203
|
+
US→EIA, EU→ENTSO-E, falling back to typical).
|
|
199
204
|
|
|
200
|
-
The data layer is a swappable repository (
|
|
201
|
-
numbers: the LLM writes the *rationale*, the code supplies the *figures*.
|
|
205
|
+
The data layer is a swappable repository (typical values by default, works offline). Nothing here
|
|
206
|
+
invents numbers: the LLM writes the *rationale*, the code supplies the *figures*.
|
|
202
207
|
|
|
203
208
|
## Honest limitations (shipped in the tool itself)
|
|
204
209
|
|
|
@@ -220,7 +225,7 @@ Or from source, for development (editable install + tests):
|
|
|
220
225
|
git clone https://github.com/bithug01/greenpick.git
|
|
221
226
|
cd greenpick
|
|
222
227
|
pip install -e ".[dev]"
|
|
223
|
-
cp .env.example .env # defaults to
|
|
228
|
+
cp .env.example .env # defaults to typical values — works offline
|
|
224
229
|
python -m greenpick # run the MCP server (stdio)
|
|
225
230
|
pytest # run tests
|
|
226
231
|
```
|
|
@@ -230,7 +235,7 @@ Connect it to an MCP client (e.g. Kiro / Claude Code / Cursor) by pointing the c
|
|
|
230
235
|
|
|
231
236
|
### Turn on live data
|
|
232
237
|
|
|
233
|
-
GreenPick runs on labelled
|
|
238
|
+
GreenPick runs on labelled typical values until you give it two free keys:
|
|
234
239
|
[EIA](https://www.eia.gov/opendata/register.php) (US grid) and
|
|
235
240
|
[ENTSO-E](https://transparency.entsoe.eu/) (EU grid). Pass them as environment variables.
|
|
236
241
|
(A `.env` file works for a source checkout; if you installed with `pip install greenpick`, use one
|
|
@@ -289,17 +294,18 @@ with open("greenpick-server.log", "w") as errlog:
|
|
|
289
294
|
Top-level `await` works in Jupyter. In a plain script, put the `with` block inside an `async def`
|
|
290
295
|
and run it with `asyncio.run(...)`.
|
|
291
296
|
|
|
292
|
-
Every carbon figure includes a `quality` field: `live` means
|
|
293
|
-
|
|
297
|
+
Every carbon figure includes a `quality` field: `live` means a current reading, `typical` means
|
|
298
|
+
this month's average from last year's real data, and `mock` means a hand-set value for a region
|
|
299
|
+
with no real data. Check it to confirm live data is on. A result that isn't live also includes a
|
|
294
300
|
`live_data_hint` saying why (no key set, the source didn't respond, or no free source covers that
|
|
295
301
|
region) and what to do about it.
|
|
296
302
|
|
|
297
303
|
## FAQ
|
|
298
304
|
|
|
299
|
-
**Do I need API keys?** No —
|
|
305
|
+
**Do I need API keys?** No — out of the box GreenPick uses *typical* values: each grid's average for
|
|
306
|
+
the current month, from a year of real hourly data, labelled `quality: "typical"`. For *live*
|
|
300
307
|
carbon, set two free keys (`EIA_API_KEY` for US, `ENTSOE_TOKEN` for EU) and
|
|
301
|
-
`GREENPICK_CARBON_SOURCE=auto` — see [Turn on live data](#turn-on-live-data).
|
|
302
|
-
works; it just says `quality: "mock"`.
|
|
308
|
+
`GREENPICK_CARBON_SOURCE=auto` — see [Turn on live data](#turn-on-live-data).
|
|
303
309
|
|
|
304
310
|
**Why "directional" and not audited numbers?** Because it's for a *design-time decision*, not a
|
|
305
311
|
compliance report. "Stockholm is ~30× cleaner than Sydney" is what you need to pick a region in
|
|
@@ -315,9 +321,10 @@ your workload leans on right now. GreenPick is deliberately location-based becau
|
|
|
315
321
|
*placement/timing* decision actually affects — you can't change the provider's annual REC portfolio,
|
|
316
322
|
but you *can* choose a cleaner grid or a cleaner hour. We ignore RECs by design, not oversight.
|
|
317
323
|
|
|
318
|
-
**What happens for a region with no live data?** It falls back to
|
|
319
|
-
|
|
320
|
-
|
|
324
|
+
**What happens for a region with no live data?** It falls back to that region's typical value for
|
|
325
|
+
the month (or, for a region no free source covers, a hand-set mock value), and labels which — it
|
|
326
|
+
never presents a fallback as a live reading. Forecast likewise degrades to the recent history, or
|
|
327
|
+
the typical day, where no day-ahead feed exists (e.g. US), labelled as such.
|
|
321
328
|
|
|
322
329
|
**Can it rank Claude vs GPT vs Llama for carbon?** No, by design. `estimate_inference_carbon` works by
|
|
323
330
|
model *size class* (self-hosted-friendly), not a named-model leaderboard — vendors don't disclose the
|
|
@@ -25,7 +25,8 @@ seconds. (It responds when you ask; auto-nudging on infra-code changes is on the
|
|
|
25
25
|
> **Status: working — 15 tools.** Live carbon for US (EIA-930) and EU (ENTSO-E), cross-cloud cost
|
|
26
26
|
> parity (Vantage-refreshed), per-axis R/Y/G ratings, carbon-aware scheduling + day-ahead forecast,
|
|
27
27
|
> GPU-training **and LLM-inference** carbon, right-sizing, serving-pattern and data-locality/residency
|
|
28
|
-
> checks — all shipped.
|
|
28
|
+
> checks — all shipped. Offline or without API keys, falls back to clearly-labelled *typical* values:
|
|
29
|
+
> this month's average for each grid, from a year of real data.
|
|
29
30
|
|
|
30
31
|
<p align="center">
|
|
31
32
|
<img src="https://raw.githubusercontent.com/bithug01/greenpick/master/docs/img/region-carbon-30x.svg" alt="Same H100 training run, ~30× the carbon depending only on region (Stockholm vs Sydney)" width="560" />
|
|
@@ -44,7 +45,7 @@ tradeoff) while you're still in the editor, before you commit.*
|
|
|
44
45
|
- ⚡ **GPU training & LLM inference carbon** — estimate a run's footprint and the greener *where* + *when*; the size/placement choices that actually move the number.
|
|
45
46
|
- 🌍 **Greener region picks** — ranked by carbon, with the cost + latency tradeoff shown, not hidden.
|
|
46
47
|
- 🛡 **Backfire guards** — flags when a greener move loses to egress, or crosses a data-residency line.
|
|
47
|
-
- 🔌 **Live US + EU grid data**,
|
|
48
|
+
- 🔌 **Live US + EU grid data**, with a realistic fallback built from a year of real data — **no API keys required to start**.
|
|
48
49
|
|
|
49
50
|
## Contents
|
|
50
51
|
|
|
@@ -156,8 +157,12 @@ See `docs/user-questions-backlog.md`.
|
|
|
156
157
|
|
|
157
158
|
- **EIA-930** (US balancing authorities) and **ENTSO-E** (EU zones) — live grid carbon,
|
|
158
159
|
auto-routed by region. Both also drive `cleanest_hours_to_run` from recent hourly history.
|
|
159
|
-
- **
|
|
160
|
-
|
|
160
|
+
- **Typical** — the offline default and fallback: for each region, this calendar month's average
|
|
161
|
+
intensity and average hour-of-day profile (UTC), computed from 12 months of real hourly EIA-930 /
|
|
162
|
+
ENTSO-E data (`data/typical.json`, refreshed by `scripts/refresh_typical.py`). Labelled
|
|
163
|
+
`quality: "typical"` — a realistic norm, never presented as a current reading.
|
|
164
|
+
- **Mock** — a hand-set illustrative value, labelled `quality: "mock"`, used only for regions no free
|
|
165
|
+
source covers (e.g. Sydney).
|
|
161
166
|
- Cost: a **dated static pricing table** (`data/pricing.json`), refreshed from live Vantage
|
|
162
167
|
pricing via `scripts/refresh_pricing.py` using one matched method across all three clouds.
|
|
163
168
|
- Instance-family efficiency: directional perf-per-watt multipliers (`data/instance_families.json`).
|
|
@@ -165,10 +170,10 @@ See `docs/user-questions-backlog.md`.
|
|
|
165
170
|
|
|
166
171
|
Set `EIA_API_KEY` and `ENTSOE_TOKEN` (both free) for live carbon — see
|
|
167
172
|
[Turn on live data](#turn-on-live-data); `GREENPICK_CARBON_SOURCE` selects the source (`auto` routes
|
|
168
|
-
US→EIA, EU→ENTSO-E,
|
|
173
|
+
US→EIA, EU→ENTSO-E, falling back to typical).
|
|
169
174
|
|
|
170
|
-
The data layer is a swappable repository (
|
|
171
|
-
numbers: the LLM writes the *rationale*, the code supplies the *figures*.
|
|
175
|
+
The data layer is a swappable repository (typical values by default, works offline). Nothing here
|
|
176
|
+
invents numbers: the LLM writes the *rationale*, the code supplies the *figures*.
|
|
172
177
|
|
|
173
178
|
## Honest limitations (shipped in the tool itself)
|
|
174
179
|
|
|
@@ -190,7 +195,7 @@ Or from source, for development (editable install + tests):
|
|
|
190
195
|
git clone https://github.com/bithug01/greenpick.git
|
|
191
196
|
cd greenpick
|
|
192
197
|
pip install -e ".[dev]"
|
|
193
|
-
cp .env.example .env # defaults to
|
|
198
|
+
cp .env.example .env # defaults to typical values — works offline
|
|
194
199
|
python -m greenpick # run the MCP server (stdio)
|
|
195
200
|
pytest # run tests
|
|
196
201
|
```
|
|
@@ -200,7 +205,7 @@ Connect it to an MCP client (e.g. Kiro / Claude Code / Cursor) by pointing the c
|
|
|
200
205
|
|
|
201
206
|
### Turn on live data
|
|
202
207
|
|
|
203
|
-
GreenPick runs on labelled
|
|
208
|
+
GreenPick runs on labelled typical values until you give it two free keys:
|
|
204
209
|
[EIA](https://www.eia.gov/opendata/register.php) (US grid) and
|
|
205
210
|
[ENTSO-E](https://transparency.entsoe.eu/) (EU grid). Pass them as environment variables.
|
|
206
211
|
(A `.env` file works for a source checkout; if you installed with `pip install greenpick`, use one
|
|
@@ -259,17 +264,18 @@ with open("greenpick-server.log", "w") as errlog:
|
|
|
259
264
|
Top-level `await` works in Jupyter. In a plain script, put the `with` block inside an `async def`
|
|
260
265
|
and run it with `asyncio.run(...)`.
|
|
261
266
|
|
|
262
|
-
Every carbon figure includes a `quality` field: `live` means
|
|
263
|
-
|
|
267
|
+
Every carbon figure includes a `quality` field: `live` means a current reading, `typical` means
|
|
268
|
+
this month's average from last year's real data, and `mock` means a hand-set value for a region
|
|
269
|
+
with no real data. Check it to confirm live data is on. A result that isn't live also includes a
|
|
264
270
|
`live_data_hint` saying why (no key set, the source didn't respond, or no free source covers that
|
|
265
271
|
region) and what to do about it.
|
|
266
272
|
|
|
267
273
|
## FAQ
|
|
268
274
|
|
|
269
|
-
**Do I need API keys?** No —
|
|
275
|
+
**Do I need API keys?** No — out of the box GreenPick uses *typical* values: each grid's average for
|
|
276
|
+
the current month, from a year of real hourly data, labelled `quality: "typical"`. For *live*
|
|
270
277
|
carbon, set two free keys (`EIA_API_KEY` for US, `ENTSOE_TOKEN` for EU) and
|
|
271
|
-
`GREENPICK_CARBON_SOURCE=auto` — see [Turn on live data](#turn-on-live-data).
|
|
272
|
-
works; it just says `quality: "mock"`.
|
|
278
|
+
`GREENPICK_CARBON_SOURCE=auto` — see [Turn on live data](#turn-on-live-data).
|
|
273
279
|
|
|
274
280
|
**Why "directional" and not audited numbers?** Because it's for a *design-time decision*, not a
|
|
275
281
|
compliance report. "Stockholm is ~30× cleaner than Sydney" is what you need to pick a region in
|
|
@@ -285,9 +291,10 @@ your workload leans on right now. GreenPick is deliberately location-based becau
|
|
|
285
291
|
*placement/timing* decision actually affects — you can't change the provider's annual REC portfolio,
|
|
286
292
|
but you *can* choose a cleaner grid or a cleaner hour. We ignore RECs by design, not oversight.
|
|
287
293
|
|
|
288
|
-
**What happens for a region with no live data?** It falls back to
|
|
289
|
-
|
|
290
|
-
|
|
294
|
+
**What happens for a region with no live data?** It falls back to that region's typical value for
|
|
295
|
+
the month (or, for a region no free source covers, a hand-set mock value), and labels which — it
|
|
296
|
+
never presents a fallback as a live reading. Forecast likewise degrades to the recent history, or
|
|
297
|
+
the typical day, where no day-ahead feed exists (e.g. US), labelled as such.
|
|
291
298
|
|
|
292
299
|
**Can it rank Claude vs GPT vs Llama for carbon?** No, by design. `estimate_inference_carbon` works by
|
|
293
300
|
model *size class* (self-hosted-friendly), not a named-model leaderboard — vendors don't disclose the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "greenpick"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Design-time sustainability co-pilot (MCP server): greener, cheaper cloud infrastructure decisions with carbon + cost + latency tradeoffs."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -4,7 +4,7 @@ Greener cloud-region choices with honest carbon + cost + latency tradeoffs,
|
|
|
4
4
|
delivered inside the vibe-coding loop. Directional guidance, not audited accounting.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.2.0"
|
|
8
8
|
|
|
9
9
|
# httpx logs every request URL at INFO, and our live sources pass their API key / token as a URL
|
|
10
10
|
# query parameter — so at the default log level the secret would land in the MCP client's logs.
|
|
@@ -7,7 +7,8 @@ from __future__ import annotations
|
|
|
7
7
|
|
|
8
8
|
from ..config import SETTINGS
|
|
9
9
|
from .repository import CarbonRepository
|
|
10
|
-
from .mock_repository import MockCarbonRepository
|
|
10
|
+
from .mock_repository import MockCarbonRepository # noqa: F401 (re-exported; tests use it)
|
|
11
|
+
from .typical_repository import TypicalCarbonRepository
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
def get_repository() -> CarbonRepository:
|
|
@@ -25,4 +26,5 @@ def get_repository() -> CarbonRepository:
|
|
|
25
26
|
# Route per region: EIA for US (eia_ba), ENTSO-E for EU (entsoe_eic), else mock.
|
|
26
27
|
from .auto_repository import AutoRepository
|
|
27
28
|
return AutoRepository(SETTINGS.eia_api_key, SETTINGS.entsoe_token, SETTINGS.cache_ttl_seconds)
|
|
28
|
-
|
|
29
|
+
# Default and 'mock' setting: typical monthly values from real data, mock only where uncovered.
|
|
30
|
+
return TypicalCarbonRepository()
|
|
@@ -9,7 +9,7 @@ Lets a single GREENPICK_CARBON_SOURCE=auto give live data on both continents.
|
|
|
9
9
|
from __future__ import annotations
|
|
10
10
|
|
|
11
11
|
from ..models import CarbonReading, Provider, HourlyCarbon, ForecastHour
|
|
12
|
-
from .
|
|
12
|
+
from .typical_repository import TypicalCarbonRepository
|
|
13
13
|
from .eia_repository import EIARepository
|
|
14
14
|
from .entsoe_repository import ENTSOERepository
|
|
15
15
|
from .static_data import region_meta
|
|
@@ -19,7 +19,7 @@ class AutoRepository:
|
|
|
19
19
|
def __init__(self, eia_key: str, entsoe_token: str, cache_ttl_seconds: int = 1800) -> None:
|
|
20
20
|
self._eia = EIARepository(eia_key, cache_ttl_seconds)
|
|
21
21
|
self._entsoe = ENTSOERepository(entsoe_token, cache_ttl_seconds)
|
|
22
|
-
self._mock =
|
|
22
|
+
self._mock = TypicalCarbonRepository()
|
|
23
23
|
|
|
24
24
|
async def carbon_now(self, provider: Provider, region: str) -> CarbonReading:
|
|
25
25
|
meta = region_meta(provider, region) or {}
|
|
@@ -22,7 +22,7 @@ from datetime import datetime, timezone
|
|
|
22
22
|
import httpx
|
|
23
23
|
|
|
24
24
|
from ..models import CarbonReading, Provider, HourlyCarbon
|
|
25
|
-
from .
|
|
25
|
+
from .typical_repository import TypicalCarbonRepository
|
|
26
26
|
from .static_data import region_meta, emission_factors
|
|
27
27
|
from ._net import REQUEST_TIMEOUT, REQUEST_DEADLINE_S, describe_error
|
|
28
28
|
|
|
@@ -36,7 +36,7 @@ class EIARepository:
|
|
|
36
36
|
self._key = api_key
|
|
37
37
|
self._ttl = cache_ttl_seconds
|
|
38
38
|
self._cache: dict[str, tuple[float, CarbonReading]] = {} # BA -> (fetched_at, reading)
|
|
39
|
-
self._fallback =
|
|
39
|
+
self._fallback = TypicalCarbonRepository()
|
|
40
40
|
self._warned_no_key = False
|
|
41
41
|
|
|
42
42
|
async def carbon_now(self, provider: Provider, region: str) -> CarbonReading:
|
|
@@ -21,7 +21,7 @@ from datetime import datetime, timezone
|
|
|
21
21
|
import httpx
|
|
22
22
|
|
|
23
23
|
from ..models import CarbonReading, Provider, HourlyCarbon
|
|
24
|
-
from .
|
|
24
|
+
from .typical_repository import TypicalCarbonRepository
|
|
25
25
|
from .static_data import region_meta
|
|
26
26
|
from ._net import REQUEST_TIMEOUT, REQUEST_DEADLINE_S, describe_error
|
|
27
27
|
|
|
@@ -35,7 +35,7 @@ class ElectricityMapsRepository:
|
|
|
35
35
|
self._token = token
|
|
36
36
|
self._ttl = cache_ttl_seconds
|
|
37
37
|
self._cache: dict[str, tuple[float, CarbonReading]] = {} # zone -> (fetched_at, reading)
|
|
38
|
-
self._fallback =
|
|
38
|
+
self._fallback = TypicalCarbonRepository()
|
|
39
39
|
self._token_rejected = False # once a 401/403 is seen, stop calling with a bad key
|
|
40
40
|
self._warned_no_token = False
|
|
41
41
|
|
|
@@ -24,7 +24,7 @@ from xml.etree import ElementTree as ET
|
|
|
24
24
|
import httpx
|
|
25
25
|
|
|
26
26
|
from ..models import CarbonReading, Provider, HourlyCarbon, ForecastHour
|
|
27
|
-
from .
|
|
27
|
+
from .typical_repository import TypicalCarbonRepository
|
|
28
28
|
from .static_data import region_meta, emission_factors
|
|
29
29
|
from ._net import REQUEST_TIMEOUT, REQUEST_DEADLINE_S, describe_error
|
|
30
30
|
|
|
@@ -75,7 +75,7 @@ class ENTSOERepository:
|
|
|
75
75
|
self._token = token
|
|
76
76
|
self._ttl = cache_ttl_seconds
|
|
77
77
|
self._cache: dict[str, tuple[float, CarbonReading]] = {} # EIC -> (fetched_at, reading)
|
|
78
|
-
self._fallback =
|
|
78
|
+
self._fallback = TypicalCarbonRepository()
|
|
79
79
|
self._warned_no_token = False
|
|
80
80
|
self._down_until: dict[str, float] = {} # EIC -> epoch secs; skip the zone until then
|
|
81
81
|
|
|
@@ -66,6 +66,16 @@ def resolve_size_class(name: str) -> tuple[str, dict] | None:
|
|
|
66
66
|
return None
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
@lru_cache(maxsize=1)
|
|
70
|
+
def typical() -> dict:
|
|
71
|
+
"""Per-region, per-calendar-month typical carbon from a year of real data (the fallback).
|
|
72
|
+
Empty if the file is absent, so GreenPick still runs (falling back to mock)."""
|
|
73
|
+
path = _DATA_DIR / "typical.json"
|
|
74
|
+
if not path.exists():
|
|
75
|
+
return {}
|
|
76
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
77
|
+
|
|
78
|
+
|
|
69
79
|
@lru_cache(maxsize=1)
|
|
70
80
|
def gpu_power() -> dict:
|
|
71
81
|
"""Directional GPU/accelerator power model (TDP + utilization + host overhead) for training."""
|