greenpick 0.6.0__tar.gz → 0.6.1__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.
Files changed (58) hide show
  1. {greenpick-0.6.0/src/greenpick.egg-info → greenpick-0.6.1}/PKG-INFO +1 -1
  2. {greenpick-0.6.0 → greenpick-0.6.1}/pyproject.toml +1 -1
  3. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/__init__.py +1 -1
  4. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/server.py +8 -4
  5. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/migration_service.py +12 -1
  6. {greenpick-0.6.0 → greenpick-0.6.1/src/greenpick.egg-info}/PKG-INFO +1 -1
  7. {greenpick-0.6.0 → greenpick-0.6.1}/tests/test_services.py +22 -0
  8. {greenpick-0.6.0 → greenpick-0.6.1}/LICENSE +0 -0
  9. {greenpick-0.6.0 → greenpick-0.6.1}/README.md +0 -0
  10. {greenpick-0.6.0 → greenpick-0.6.1}/setup.cfg +0 -0
  11. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/__main__.py +0 -0
  12. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/config.py +0 -0
  13. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/__init__.py +0 -0
  14. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/_net.py +0 -0
  15. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/annual.json +0 -0
  16. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/auto_repository.py +0 -0
  17. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/canada_repository.py +0 -0
  18. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/eia_repository.py +0 -0
  19. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/electricitymaps_repository.py +0 -0
  20. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/emission_factors.json +0 -0
  21. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/entsoe_repository.py +0 -0
  22. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/gpu_power.json +0 -0
  23. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/inference_energy.json +0 -0
  24. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/instance_families.json +0 -0
  25. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/mock_repository.py +0 -0
  26. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/network.json +0 -0
  27. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/power.json +0 -0
  28. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/pricing.json +0 -0
  29. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/regions.json +0 -0
  30. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/repository.py +0 -0
  31. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/static_data.py +0 -0
  32. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/typical.json +0 -0
  33. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/data/typical_repository.py +0 -0
  34. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/models.py +0 -0
  35. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/__init__.py +0 -0
  36. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/carbon_service.py +0 -0
  37. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/cost_service.py +0 -0
  38. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/data_hint.py +0 -0
  39. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/forecast_service.py +0 -0
  40. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/inference_service.py +0 -0
  41. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/instance_service.py +0 -0
  42. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/justification_service.py +0 -0
  43. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/latency_service.py +0 -0
  44. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/locality_service.py +0 -0
  45. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/metric_scaffold_service.py +0 -0
  46. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/offhours_service.py +0 -0
  47. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/recommender.py +0 -0
  48. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/residency_service.py +0 -0
  49. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/rightsizing_service.py +0 -0
  50. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/scheduling_service.py +0 -0
  51. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/serving_service.py +0 -0
  52. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/training_service.py +0 -0
  53. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick/services/typical_ranking_service.py +0 -0
  54. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick.egg-info/SOURCES.txt +0 -0
  55. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick.egg-info/dependency_links.txt +0 -0
  56. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick.egg-info/entry_points.txt +0 -0
  57. {greenpick-0.6.0 → greenpick-0.6.1}/src/greenpick.egg-info/requires.txt +0 -0
  58. {greenpick-0.6.0 → greenpick-0.6.1}/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.0
3
+ Version: 0.6.1
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "greenpick"
3
- version = "0.6.0"
3
+ version = "0.6.1"
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.0"
7
+ __version__ = "0.6.1"
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.
@@ -81,7 +81,8 @@ async def compare_regions_tri_axis(
81
81
  (e.g. ["us-east-1","us-west-2","eu-west-1"]) to rank ONLY those you'd actually consider,
82
82
  instead of the whole catalog — your baseline is always kept in the comparison. If the pick is
83
83
  in a different region from where your data lives, run data_locality_egress_check before moving:
84
- cross-region/cross-cloud egress can erase the carbon/cost win."""
84
+ cross-region/cross-cloud egress can erase the carbon/cost win. To write a chosen move up for a pull
85
+ request or decision record, use region_move_justification (do not draft the text from this result)."""
85
86
  rec = await recommender.compare_regions(
86
87
  _repo,
87
88
  providers=[p for p in (providers or [])] or None, # type: ignore[list-item]
@@ -111,7 +112,8 @@ async def greener_alternative_to(
111
112
  with the carbon/cost/latency delta vs. your current choice. The core 'nudge' tool. Pass
112
113
  `regions` to limit the search to options you'd actually move to; your current region is
113
114
  always included. Before accepting a move, run data_locality_egress_check if your data stays in
114
- the current region — the egress to feed compute from afar can outweigh the greener-region win."""
115
+ the current region — the egress to feed compute from afar can outweigh the greener-region win.
116
+ To write a chosen move up for a pull request or decision record, use region_move_justification."""
115
117
  rec = await recommender.compare_regions(
116
118
  _repo,
117
119
  providers=[p for p in (providers or [])] or None, # type: ignore[list-item]
@@ -385,8 +387,10 @@ async def migration_payoff(
385
387
  data_locality_egress_check. Give where the data is now, where compute runs, how many GB you would
386
388
  migrate once, and how many GB a month cross between them today. Returns the payback in months, the
387
389
  one-off vs monthly egress cost and transfer carbon, and the net over the horizon (default 12 months).
388
- Egress only: storage prices, request charges, downtime and effort are not included. Ask the user for
389
- the GB figures rather than assuming them."""
390
+ Egress only: storage prices, request charges, downtime and effort are not included, and it does NOT
391
+ judge whether the compute should be in that region (use region_move_justification for that). Quote the
392
+ FULL dollar range (low and high), not just the high end; it can straddle zero. Ask the user for the GB
393
+ figures rather than assuming them."""
390
394
  if data_gb_to_migrate <= 0 or monthly_cross_boundary_gb < 0 or not 0 < horizon_months <= 120:
391
395
  return {"error": "data_gb_to_migrate must be > 0, monthly_cross_boundary_gb >= 0, and "
392
396
  "horizon_months between 0 and 120."}
@@ -22,6 +22,10 @@ from ..data.static_data import network_model
22
22
  from . import carbon_service, locality_service, residency_service
23
23
 
24
24
 
25
+ def _usd(x: float) -> str:
26
+ return f"-${abs(x):,.0f}" if x < 0 else f"${x:,.0f}"
27
+
28
+
25
29
  async def migration_payoff(
26
30
  repo: CarbonRepository,
27
31
  data_provider: str, data_region: str,
@@ -69,6 +73,8 @@ async def migration_payoff(
69
73
  residency = residency_service.residency_flag(
70
74
  data_provider, data_region, compute_provider, compute_region)
71
75
 
76
+ straddle = (net_usd is not None and net_usd[0] < 0 < net_usd[1])
77
+ egress_only = " Egress only: this does not judge whether the compute should be in that region."
72
78
  if monthly_cross_boundary_gb <= 0:
73
79
  verdict = "no_ongoing_traffic"
74
80
  summary = (f"Moving the data buys no egress saving: no GB a month cross between "
@@ -79,12 +85,17 @@ async def migration_payoff(
79
85
  summary = (f"Moving the data too pays back in about {payback_months:g} months "
80
86
  f"(~{data_gb_to_migrate:,.0f} GB once vs ~{monthly_cross_boundary_gb:,.0f} GB a month "
81
87
  f"no longer crossing), within your {horizon_months:g}-month horizon.")
88
+ if straddle:
89
+ summary += (f" In dollars it could land either side of zero over the horizon "
90
+ f"(net {_usd(net_usd[0])} to {_usd(net_usd[1])}); the carbon saving is "
91
+ f"{'positive' if net_kg and net_kg > 0 else 'not positive'} (~{net_kg:,.1f} kg).")
92
+ summary += egress_only
82
93
  else:
83
94
  verdict = "does_not_pay_back"
84
95
  summary = (f"Moving the data too does NOT pay back within {horizon_months:g} months: it takes "
85
96
  f"about {payback_months:g} months (~{data_gb_to_migrate:,.0f} GB once vs "
86
97
  f"~{monthly_cross_boundary_gb:,.0f} GB a month no longer crossing). Leave the data "
87
- "where it is, or re-check the volumes.")
98
+ "where it is, or re-check the volumes.") + egress_only
88
99
 
89
100
  return {
90
101
  "summary": summary,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: greenpick
3
- Version: 0.6.0
3
+ Version: 0.6.1
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
@@ -1968,3 +1968,25 @@ async def test_new_tools_validate_inputs():
1968
1968
  assert "error" in bad
1969
1969
  bad2 = await _tool("region_move_justification")("aws", "us-east-1", "aws", "eu-north-1", 4, 16, hours_per_month=0)
1970
1970
  assert "error" in bad2
1971
+
1972
+
1973
+ # --- 0.6.1: migration summary is honest about a dollar range that straddles zero ---
1974
+ @pytest.mark.asyncio
1975
+ async def test_migration_summary_flags_dollar_range_straddling_zero():
1976
+ from greenpick.services import migration_service as ms
1977
+ args = ("aws", "us-east-1", "aws", "eu-north-1")
1978
+ r = await ms.migration_payoff(MockCarbonRepository(), *args, 2000, 300)
1979
+ assert r["verdict"] == "pays_back" and r["net_over_horizon_usd_low"] < 0 < r["net_over_horizon_usd_high"]
1980
+ assert "either side of zero" in r["summary"] and "Egress only" in r["summary"]
1981
+ clear = await ms.migration_payoff(MockCarbonRepository(), *args, 500, 300)
1982
+ assert "either side of zero" not in clear["summary"] and "Egress only" in clear["summary"]
1983
+ assert "Egress only" in (await ms.migration_payoff(MockCarbonRepository(), *args, 8000, 300))["summary"]
1984
+
1985
+
1986
+ def test_move_tools_point_to_the_justification_tool():
1987
+ import asyncio
1988
+ from greenpick import server
1989
+ desc = {t.name: t.description for t in asyncio.run(server.mcp.list_tools())}
1990
+ assert "region_move_justification" in desc["compare_regions_tri_axis"]
1991
+ assert "region_move_justification" in desc["greener_alternative_to"]
1992
+ assert "FULL dollar range" in desc["migration_payoff"]
File without changes
File without changes
File without changes