greenpick 0.6.2__tar.gz → 0.6.3__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.6.2/src/greenpick.egg-info → greenpick-0.6.3}/PKG-INFO +3 -1
- {greenpick-0.6.2 → greenpick-0.6.3}/README.md +2 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/pyproject.toml +1 -1
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/__init__.py +1 -1
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/server.py +5 -3
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/data_hint.py +4 -2
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/justification_service.py +5 -1
- greenpick-0.6.3/src/greenpick/services/sources.py +101 -0
- {greenpick-0.6.2 → greenpick-0.6.3/src/greenpick.egg-info}/PKG-INFO +3 -1
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick.egg-info/SOURCES.txt +1 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/tests/test_services.py +59 -1
- {greenpick-0.6.2 → greenpick-0.6.3}/LICENSE +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/setup.cfg +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/__main__.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/config.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/__init__.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/_net.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/annual.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/auto_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/canada_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/eia_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/electricitymaps_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/emission_factors.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/entsoe_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/gpu_power.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/inference_energy.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/instance_families.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/mock_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/network.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/power.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/pricing.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/regions.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/static_data.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/typical.json +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/data/typical_repository.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/models.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/__init__.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/carbon_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/cost_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/forecast_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/inference_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/instance_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/latency_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/locality_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/metric_scaffold_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/migration_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/offhours_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/recommender.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/residency_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/rightsizing_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/scheduling_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/serving_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/training_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick/services/typical_ranking_service.py +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick.egg-info/dependency_links.txt +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick.egg-info/entry_points.txt +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/src/greenpick.egg-info/requires.txt +0 -0
- {greenpick-0.6.2 → greenpick-0.6.3}/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.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -226,6 +226,8 @@ flow-traced consumption. Cost is a ballpark, not a quote. Latency is a geographi
|
|
|
226
226
|
|
|
227
227
|
## Quick start
|
|
228
228
|
|
|
229
|
+
<a href="https://mcprush.com/bithug01/greenpick-mcp"><img src="https://mcprush.com/badge/greenpick-mcp/card.svg" width="220" alt="GreenPick MCP server on mcprush" /></a>
|
|
230
|
+
|
|
229
231
|
```bash
|
|
230
232
|
# Python 3.11+
|
|
231
233
|
pip install greenpick # from PyPI
|
|
@@ -196,6 +196,8 @@ flow-traced consumption. Cost is a ballpark, not a quote. Latency is a geographi
|
|
|
196
196
|
|
|
197
197
|
## Quick start
|
|
198
198
|
|
|
199
|
+
<a href="https://mcprush.com/bithug01/greenpick-mcp"><img src="https://mcprush.com/badge/greenpick-mcp/card.svg" width="220" alt="GreenPick MCP server on mcprush" /></a>
|
|
200
|
+
|
|
199
201
|
```bash
|
|
200
202
|
# Python 3.11+
|
|
201
203
|
pip install greenpick # from PyPI
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "greenpick"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.3"
|
|
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.6.
|
|
7
|
+
__version__ = "0.6.3"
|
|
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.
|
|
@@ -30,6 +30,7 @@ from .services import (
|
|
|
30
30
|
)
|
|
31
31
|
from .models import ServingHub
|
|
32
32
|
from .services.data_hint import with_hint
|
|
33
|
+
from .services.sources import with_sources
|
|
33
34
|
from .data.typical_repository import typical_context
|
|
34
35
|
|
|
35
36
|
# Sent to every MCP client at connect. Agents learn a server from its tool list, not its README, and
|
|
@@ -54,7 +55,8 @@ for a pull request, use region_move_justification.
|
|
|
54
55
|
Every carbon figure carries source and quality fields. Report 'typical', 'mock', 'cached' or \
|
|
55
56
|
'historical' values as such, never as live readings — including figures derived from them, such as a kg CO2 saving. \
|
|
56
57
|
When a result includes live_data_hint, pass it on: it says why the data isn't live and how to turn \
|
|
57
|
-
live data on.
|
|
58
|
+
live data on. Results with carbon or cost figures include verify_at: the public page behind each \
|
|
59
|
+
figure, for anyone who wants to check it. If a call fails or output is missing, say so rather than describing \
|
|
58
60
|
what the tool would return. GreenPick estimates; it does not measure running systems or report model \
|
|
59
61
|
accuracy. Use explain_methodology_and_limits when asked how a number was produced.\
|
|
60
62
|
"""
|
|
@@ -308,9 +310,9 @@ def greenest_regions_on_average(
|
|
|
308
310
|
norm rather than a single hour; use region_carbon_now for what the grid is doing today. Carbon only
|
|
309
311
|
(no cost or latency; compare_regions_tri_axis weighs all three). Offline, needs no keys. Regions
|
|
310
312
|
labelled annual_average have only a yearly national figure and are not month-specific."""
|
|
311
|
-
return typical_ranking_service.rank_regions(
|
|
313
|
+
return with_sources(typical_ranking_service.rank_regions(
|
|
312
314
|
month=month, whole_year=whole_year, providers=providers, regions=regions, top_n=top_n,
|
|
313
|
-
)
|
|
315
|
+
))
|
|
314
316
|
|
|
315
317
|
|
|
316
318
|
@mcp.tool(annotations=LIVE_DATA_TOOL)
|
|
@@ -180,8 +180,10 @@ def build_hint(result: dict) -> Optional[str]:
|
|
|
180
180
|
|
|
181
181
|
|
|
182
182
|
def with_hint(result: dict) -> dict:
|
|
183
|
-
"""Add `live_data_hint` to a tool result that uses fallback data
|
|
183
|
+
"""Add `live_data_hint` to a tool result that uses fallback data, and `verify_at` (where to check
|
|
184
|
+
its carbon and cost figures) to any result that has them. Existing fields are left unchanged."""
|
|
185
|
+
from .sources import with_sources
|
|
184
186
|
hint = build_hint(result)
|
|
185
187
|
if hint:
|
|
186
188
|
result["live_data_hint"] = hint
|
|
187
|
-
return result
|
|
189
|
+
return with_sources(result)
|
|
@@ -122,7 +122,11 @@ async def region_move_justification(
|
|
|
122
122
|
if net_kg is not None:
|
|
123
123
|
cost_part = (f" and about ${egress_low:,.0f}-${egress_high:,.0f} a month in egress"
|
|
124
124
|
if egress_low is not None else "")
|
|
125
|
-
if
|
|
125
|
+
if kg_saving <= 0:
|
|
126
|
+
parts.append(
|
|
127
|
+
f"The data stays in {from_provider}/{from_region} (~{monthly_cross_boundary_gb:,.0f} GB a "
|
|
128
|
+
f"month crossing), which adds about {transfer_kg:.1f} kg CO2{cost_part} on top.")
|
|
129
|
+
elif net_kg > 0:
|
|
126
130
|
parts.append(
|
|
127
131
|
f"The data stays in {from_provider}/{from_region} (~{monthly_cross_boundary_gb:,.0f} GB a "
|
|
128
132
|
f"month crossing), which adds about {transfer_kg:.1f} kg CO2{cost_part}, "
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""Where to check a number: the primary source behind each figure GreenPick reports.
|
|
2
|
+
|
|
3
|
+
GreenPick's estimates are directional, and its results say so. "Go and verify" is only useful if
|
|
4
|
+
it says WHERE, so every result that contains a carbon figure or a dollar figure also gets a short
|
|
5
|
+
`verify_at` list: which kind of figure, what it is based on, and the public page to check it against.
|
|
6
|
+
|
|
7
|
+
Kept deliberately small and stable: top-level pages that publish the data (grid operators, the
|
|
8
|
+
cloud providers' own pricing pages), not deep links that move. Additive only: it never changes or
|
|
9
|
+
removes an existing field.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any, Iterator
|
|
14
|
+
|
|
15
|
+
# Carbon: the base source name (after stripping "typical_") -> (what it is, where to check it).
|
|
16
|
+
_CARBON = {
|
|
17
|
+
"eia": ("US grid data from EIA-930 (hourly generation by fuel)",
|
|
18
|
+
"https://www.eia.gov/electricity/gridmonitor/"),
|
|
19
|
+
"entsoe": ("EU grid data from the ENTSO-E Transparency Platform (generation by fuel)",
|
|
20
|
+
"https://transparency.entsoe.eu/"),
|
|
21
|
+
"hq": ("Québec grid data from Hydro-Québec open data", "https://donnees.hydroquebec.com/"),
|
|
22
|
+
"ieso": ("Ontario grid data from IESO", "https://www.ieso.ca/en/Power-Data"),
|
|
23
|
+
"ember_annual": ("Yearly national grid intensity from Ember, via Our World in Data",
|
|
24
|
+
"https://ourworldindata.org/grapher/carbon-intensity-electricity"),
|
|
25
|
+
"electricitymaps": ("Grid data from Electricity Maps", "https://www.electricitymaps.com/"),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# Cost: provider -> (compute price page, data-transfer price page).
|
|
29
|
+
_PRICING = {
|
|
30
|
+
"aws": ("https://aws.amazon.com/ec2/pricing/on-demand/",
|
|
31
|
+
"https://aws.amazon.com/ec2/pricing/on-demand/"),
|
|
32
|
+
"gcp": ("https://cloud.google.com/compute/vm-instance-pricing",
|
|
33
|
+
"https://cloud.google.com/vpc/network-pricing"),
|
|
34
|
+
"azure": ("https://azure.microsoft.com/en-us/pricing/details/virtual-machines/linux/",
|
|
35
|
+
"https://azure.microsoft.com/en-us/pricing/details/bandwidth/"),
|
|
36
|
+
}
|
|
37
|
+
_PROVIDER_NAME = {"aws": "AWS", "gcp": "Google Cloud", "azure": "Azure"}
|
|
38
|
+
|
|
39
|
+
_SOURCE_KEYS = ("source", "carbon_source", "transfer_carbon_source",
|
|
40
|
+
"from_carbon_source", "to_carbon_source")
|
|
41
|
+
_PROVIDER_KEYS = ("provider", "compute_provider", "data_provider", "from_provider", "to_provider")
|
|
42
|
+
_LOCATION_KEYS = ("recommended_region", "compute_location", "data_location", "from", "to")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _walk(obj: Any) -> Iterator[tuple[str, Any]]:
|
|
46
|
+
if isinstance(obj, dict):
|
|
47
|
+
for k, v in obj.items():
|
|
48
|
+
yield k, v
|
|
49
|
+
yield from _walk(v)
|
|
50
|
+
elif isinstance(obj, list):
|
|
51
|
+
for v in obj:
|
|
52
|
+
yield from _walk(v)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def verify_at(result: dict) -> list[dict]:
|
|
56
|
+
"""The primary sources behind the carbon and cost figures in a tool result (may be empty)."""
|
|
57
|
+
carbon: set[str] = set()
|
|
58
|
+
providers: set[str] = set()
|
|
59
|
+
has_cost = has_egress = False
|
|
60
|
+
for key, value in _walk(result):
|
|
61
|
+
if key in _SOURCE_KEYS and isinstance(value, str):
|
|
62
|
+
base = value[len("typical_"):] if value.startswith("typical_") else value
|
|
63
|
+
if base in _CARBON:
|
|
64
|
+
carbon.add(base)
|
|
65
|
+
elif key in _PROVIDER_KEYS and value in _PRICING:
|
|
66
|
+
providers.add(value)
|
|
67
|
+
elif key in _LOCATION_KEYS and isinstance(value, str) and "/" in value:
|
|
68
|
+
prov = value.split("/", 1)[0]
|
|
69
|
+
if prov in _PRICING:
|
|
70
|
+
providers.add(prov)
|
|
71
|
+
if isinstance(value, (int, float)) and not isinstance(value, bool):
|
|
72
|
+
if "usd" not in key:
|
|
73
|
+
continue
|
|
74
|
+
if "egress" in key:
|
|
75
|
+
has_egress = True
|
|
76
|
+
elif "cost" in key or "saving" in key: # compute cost or a saving on it
|
|
77
|
+
has_cost = True
|
|
78
|
+
|
|
79
|
+
out: list[dict] = []
|
|
80
|
+
for base in sorted(carbon):
|
|
81
|
+
what, url = _CARBON[base]
|
|
82
|
+
out.append({"figure": "grid carbon intensity", "based_on": what, "url": url})
|
|
83
|
+
for prov in sorted(providers):
|
|
84
|
+
compute_url, transfer_url = _PRICING[prov]
|
|
85
|
+
name = _PROVIDER_NAME[prov]
|
|
86
|
+
if has_cost:
|
|
87
|
+
out.append({"figure": "compute cost", "based_on": f"{name} on-demand list prices (dated)",
|
|
88
|
+
"url": compute_url})
|
|
89
|
+
if has_egress:
|
|
90
|
+
out.append({"figure": "data-transfer (egress) cost",
|
|
91
|
+
"based_on": f"{name} data-transfer prices (dated)", "url": transfer_url})
|
|
92
|
+
return out
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def with_sources(result: dict) -> dict:
|
|
96
|
+
"""Add `verify_at` to a result that contains carbon or cost figures; leave others unchanged."""
|
|
97
|
+
if isinstance(result, dict) and "error" not in result:
|
|
98
|
+
links = verify_at(result)
|
|
99
|
+
if links:
|
|
100
|
+
result["verify_at"] = links
|
|
101
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: greenpick
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -226,6 +226,8 @@ flow-traced consumption. Cost is a ballpark, not a quote. Latency is a geographi
|
|
|
226
226
|
|
|
227
227
|
## Quick start
|
|
228
228
|
|
|
229
|
+
<a href="https://mcprush.com/bithug01/greenpick-mcp"><img src="https://mcprush.com/badge/greenpick-mcp/card.svg" width="220" alt="GreenPick MCP server on mcprush" /></a>
|
|
230
|
+
|
|
229
231
|
```bash
|
|
230
232
|
# Python 3.11+
|
|
231
233
|
pip install greenpick # from PyPI
|
|
@@ -51,6 +51,7 @@ src/greenpick/services/residency_service.py
|
|
|
51
51
|
src/greenpick/services/rightsizing_service.py
|
|
52
52
|
src/greenpick/services/scheduling_service.py
|
|
53
53
|
src/greenpick/services/serving_service.py
|
|
54
|
+
src/greenpick/services/sources.py
|
|
54
55
|
src/greenpick/services/training_service.py
|
|
55
56
|
src/greenpick/services/typical_ranking_service.py
|
|
56
57
|
tests/test_services.py
|
|
@@ -1284,7 +1284,7 @@ def test_server_sends_instructions_that_lead_with_rightsizing():
|
|
|
1284
1284
|
assert "mock" in text and "TOTAL" in text and "data_locality_egress_check" in text
|
|
1285
1285
|
# Every tool named in the instructions actually exists.
|
|
1286
1286
|
import re, asyncio
|
|
1287
|
-
named = set(re.findall(r"\b[a-z]+(?:_[a-z]+)+\b", text)) - {"wall_clock_hours", "gpu_hours", "residency_note", "live_data_hint"}
|
|
1287
|
+
named = set(re.findall(r"\b[a-z]+(?:_[a-z]+)+\b", text)) - {"wall_clock_hours", "gpu_hours", "residency_note", "live_data_hint", "verify_at"}
|
|
1288
1288
|
registered = {t.name for t in asyncio.run(server.mcp.list_tools())}
|
|
1289
1289
|
assert named <= registered, named - registered
|
|
1290
1290
|
|
|
@@ -2033,3 +2033,61 @@ async def test_rightsizing_fraction_still_works_and_ram_is_checked(monkeypatch):
|
|
|
2033
2033
|
async def test_training_rejects_percentage_utilization():
|
|
2034
2034
|
r = await _tool("estimate_training_carbon")(gpu="a100", gpu_count=8, gpu_hours=40, utilization=60)
|
|
2035
2035
|
assert "error" in r and "Did you mean 0.6?" in r["error"]
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
# --- 0.6.3: verify_at says where to check each carbon and cost figure (additive only) ---
|
|
2039
|
+
def test_verify_at_maps_sources_and_costs():
|
|
2040
|
+
from greenpick.services.sources import verify_at
|
|
2041
|
+
r = {"carbon_source": "typical_eia", "provider": "aws", "cost_now_usd_per_month": 10.0}
|
|
2042
|
+
figs = {(v["figure"], v["url"]) for v in verify_at(r)}
|
|
2043
|
+
assert ("grid carbon intensity", "https://www.eia.gov/electricity/gridmonitor/") in figs
|
|
2044
|
+
assert ("compute cost", "https://aws.amazon.com/ec2/pricing/on-demand/") in figs
|
|
2045
|
+
eg = verify_at({"data_location": "gcp/us-east4", "est_monthly_egress_usd_low": 1.0, "transfer_carbon_source": "ember_annual"})
|
|
2046
|
+
assert {v["figure"] for v in eg} == {"data-transfer (egress) cost", "grid carbon intensity"}
|
|
2047
|
+
assert verify_at({"provider": "aws", "net_over_horizon_usd_low": 3.0}) == [] # not a compute cost
|
|
2048
|
+
assert verify_at({"note": "nothing to check"}) == []
|
|
2049
|
+
|
|
2050
|
+
|
|
2051
|
+
@pytest.mark.asyncio
|
|
2052
|
+
async def test_tools_add_verify_at_but_not_on_errors(monkeypatch):
|
|
2053
|
+
from greenpick import server
|
|
2054
|
+
monkeypatch.setattr(server, "_repo", TypicalCarbonRepository())
|
|
2055
|
+
r = await _tool("rightsizing_check")(provider="aws", region="us-east-1", vcpu=8, ram_gb=32, cpu_utilization=0.15)
|
|
2056
|
+
assert {v["figure"] for v in r["verify_at"]} == {"grid carbon intensity", "compute cost"}
|
|
2057
|
+
rank = _tool("greenest_regions_on_average")(month=7, regions=["us-west-2", "ap-northeast-1"])
|
|
2058
|
+
assert any("ourworldindata" in v["url"] for v in rank["verify_at"])
|
|
2059
|
+
bad = await _tool("rightsizing_check")(provider="aws", region="us-east-1", vcpu=8, ram_gb=32, cpu_utilization=15)
|
|
2060
|
+
assert "error" in bad and "verify_at" not in bad
|
|
2061
|
+
|
|
2062
|
+
|
|
2063
|
+
# --- The notebook starter prompt (docs/using-from-a-notebook.md) relies on these fields. ---
|
|
2064
|
+
# Testers and workshop attendees run that prompt against whatever version pip installs, so a
|
|
2065
|
+
# release must never rename or drop them.
|
|
2066
|
+
@pytest.mark.asyncio
|
|
2067
|
+
async def test_fields_the_notebook_prompt_relies_on(monkeypatch):
|
|
2068
|
+
from greenpick import server
|
|
2069
|
+
monkeypatch.setattr(server, "_repo", TypicalCarbonRepository())
|
|
2070
|
+
now = await _tool("region_carbon_now")(provider="aws", region="us-east-1")
|
|
2071
|
+
assert {"source", "quality", "live_data_hint"} <= set(now)
|
|
2072
|
+
eg = await _tool("data_locality_egress_check")(compute_provider="aws", compute_region="eu-north-1",
|
|
2073
|
+
data_provider="aws", data_region="us-east-1",
|
|
2074
|
+
monthly_cross_boundary_gb=100)
|
|
2075
|
+
assert next(iter(eg)) == "summary" and eg["summary"].startswith("NO NET VERDICT")
|
|
2076
|
+
assert {"residency_note", "limitations"} <= set(eg)
|
|
2077
|
+
mp = await _tool("migration_payoff")("aws", "us-east-1", "aws", "eu-north-1", 2000, 300)
|
|
2078
|
+
assert next(iter(mp)) == "summary" and "net_over_horizon_usd_low" in mp and "net_over_horizon_usd_high" in mp
|
|
2079
|
+
pr = await _tool("region_move_justification")("aws", "us-east-1", "aws", "eu-north-1", 4, 16)
|
|
2080
|
+
assert next(iter(pr)) == "summary" and pr["pr_text"]
|
|
2081
|
+
off = await _tool("offhours_check")(provider="aws", region="us-east-1", vcpu=4, ram_gb=16, hours_per_week_on=50)
|
|
2082
|
+
assert {"monthly_usd_saving", "limitations"} <= set(off)
|
|
2083
|
+
assert "error" in await _tool("offhours_check")(provider="aws", region="us-east-1", vcpu=4, ram_gb=16, hours_per_week_on=200)
|
|
2084
|
+
assert "error" in await _tool("rightsizing_check")(provider="aws", region="us-east-1", vcpu=8, ram_gb=32, cpu_utilization=15)
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
@pytest.mark.asyncio
|
|
2088
|
+
async def test_reverse_pr_text_does_not_repeat_backfire():
|
|
2089
|
+
from greenpick.services import justification_service as js
|
|
2090
|
+
r = await js.region_move_justification(MockCarbonRepository(), "aws", "eu-north-1", "aws", "us-east-1",
|
|
2091
|
+
4, 16, monthly_cross_boundary_gb=100)
|
|
2092
|
+
assert "would RAISE" in r["pr_text"] and "backfires" not in r["pr_text"]
|
|
2093
|
+
assert "unless the data moves too" not in r["pr_text"] and "on top." in r["pr_text"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|