greenpick 0.5.0__tar.gz → 0.5.2__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 (56) hide show
  1. {greenpick-0.5.0/src/greenpick.egg-info → greenpick-0.5.2}/PKG-INFO +1 -1
  2. {greenpick-0.5.0 → greenpick-0.5.2}/pyproject.toml +1 -1
  3. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/__init__.py +1 -1
  4. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/models.py +2 -0
  5. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/server.py +3 -1
  6. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/locality_service.py +14 -4
  7. {greenpick-0.5.0 → greenpick-0.5.2/src/greenpick.egg-info}/PKG-INFO +1 -1
  8. {greenpick-0.5.0 → greenpick-0.5.2}/tests/test_services.py +27 -0
  9. {greenpick-0.5.0 → greenpick-0.5.2}/LICENSE +0 -0
  10. {greenpick-0.5.0 → greenpick-0.5.2}/README.md +0 -0
  11. {greenpick-0.5.0 → greenpick-0.5.2}/setup.cfg +0 -0
  12. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/__main__.py +0 -0
  13. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/config.py +0 -0
  14. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/__init__.py +0 -0
  15. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/_net.py +0 -0
  16. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/annual.json +0 -0
  17. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/auto_repository.py +0 -0
  18. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/canada_repository.py +0 -0
  19. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/eia_repository.py +0 -0
  20. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/electricitymaps_repository.py +0 -0
  21. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/emission_factors.json +0 -0
  22. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/entsoe_repository.py +0 -0
  23. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/gpu_power.json +0 -0
  24. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/inference_energy.json +0 -0
  25. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/instance_families.json +0 -0
  26. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/mock_repository.py +0 -0
  27. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/network.json +0 -0
  28. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/power.json +0 -0
  29. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/pricing.json +0 -0
  30. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/regions.json +0 -0
  31. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/repository.py +0 -0
  32. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/static_data.py +0 -0
  33. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/typical.json +0 -0
  34. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/data/typical_repository.py +0 -0
  35. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/__init__.py +0 -0
  36. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/carbon_service.py +0 -0
  37. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/cost_service.py +0 -0
  38. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/data_hint.py +0 -0
  39. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/forecast_service.py +0 -0
  40. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/inference_service.py +0 -0
  41. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/instance_service.py +0 -0
  42. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/latency_service.py +0 -0
  43. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/metric_scaffold_service.py +0 -0
  44. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/offhours_service.py +0 -0
  45. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/recommender.py +0 -0
  46. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/residency_service.py +0 -0
  47. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/rightsizing_service.py +0 -0
  48. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/scheduling_service.py +0 -0
  49. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/serving_service.py +0 -0
  50. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/training_service.py +0 -0
  51. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick/services/typical_ranking_service.py +0 -0
  52. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick.egg-info/SOURCES.txt +0 -0
  53. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick.egg-info/dependency_links.txt +0 -0
  54. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick.egg-info/entry_points.txt +0 -0
  55. {greenpick-0.5.0 → greenpick-0.5.2}/src/greenpick.egg-info/requires.txt +0 -0
  56. {greenpick-0.5.0 → greenpick-0.5.2}/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.5.0
3
+ Version: 0.5.2
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.5.0"
3
+ version = "0.5.2"
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.5.0"
7
+ __version__ = "0.5.2"
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.
@@ -303,6 +303,7 @@ class DataLocalityWarning(BaseModel):
303
303
 
304
304
  The core 'does the greener-region move backfire?' check. Primary signal is egress COST;
305
305
  transfer carbon is a secondary, deliberately-coarse figure."""
306
+ summary: str = "" # one line, FIRST key: the answer or what is missing
306
307
  compute_location: str # "provider/region"
307
308
  data_location: str # "provider/region"
308
309
  monthly_cross_boundary_gb: float
@@ -317,6 +318,7 @@ class DataLocalityWarning(BaseModel):
317
318
  transfer_carbon_quality: Optional[str] = None
318
319
  carbon_saving_kg_co2_from_move: Optional[float] = None # echoed back if provided
319
320
  net_carbon_verdict: Optional[str] = None # set only when a saving was supplied
321
+ verdict_status: str = "not_evaluated" # 'evaluated' only when a saving was supplied
320
322
  residency_note: Optional[str] = None # neutral cross-jurisdiction heads-up, if any
321
323
  warning: str = ""
322
324
  recommendation: str = ""
@@ -358,7 +358,9 @@ async def data_locality_egress_check(
358
358
  compute runs, where the data lives (provider+region), and monthly GB crossing between them.
359
359
  Returns a directional egress-COST ballpark, a coarse transfer-carbon figure, and — if you
360
360
  pass the carbon saving the greener-region move buys — whether it's still a net win. Use this
361
- before accepting a greener-region suggestion when your data stays put."""
361
+ before accepting a greener-region suggestion when your data stays put. Use the user's real GB and
362
+ data location; if unknown, ask rather than assume. If verdict_status is 'not_evaluated' there is NO
363
+ answer yet to 'does it backfire?': say so, and get carbon_saving_kg_co2_from_move first."""
362
364
  advice = await locality_service.data_locality_check(
363
365
  _repo, compute_provider, compute_region, data_provider, data_region, # type: ignore[arg-type]
364
366
  monthly_cross_boundary_gb=monthly_cross_boundary_gb,
@@ -78,6 +78,7 @@ async def data_locality_check(
78
78
  egress_rate_usd_per_gb_low=0.0, egress_rate_usd_per_gb_high=0.0,
79
79
  est_monthly_egress_usd_low=0.0, est_monthly_egress_usd_high=0.0,
80
80
  est_monthly_transfer_kg_co2=0.0,
81
+ summary="No cross-boundary egress: compute and data are in the same region.",
81
82
  warning="Compute and data are co-located — no cross-boundary egress for this flow.",
82
83
  recommendation="No locality penalty. Choose the region on carbon/latency/cost as usual.",
83
84
  limitations=LIMITATIONS,
@@ -109,7 +110,9 @@ async def data_locality_check(
109
110
 
110
111
  # Net verdict only if the caller told us what the move saves.
111
112
  verdict = None
113
+ verdict_status = "not_evaluated"
112
114
  if carbon_saving_kg_co2_from_move is not None:
115
+ verdict_status = "evaluated"
113
116
  net = carbon_saving_kg_co2_from_move - transfer_kg
114
117
  if net > 0:
115
118
  verdict = (f"Still a net win: the move saves ~{carbon_saving_kg_co2_from_move:.1f} kg "
@@ -130,10 +133,17 @@ async def data_locality_check(
130
133
  f"cross the boundary, costing {cost_txt}, plus ~{transfer_kg:.1f} kg CO2/mo of extra "
131
134
  f"transfer. A greener-region move can be undone by this.")
132
135
  if verdict is None:
133
- # Point the caller at the optional input that turns this into a net win/backfire verdict.
134
- warning += (" For a net verdict, pass carbon_saving_kg_co2_from_move (the kg CO2/mo the move "
135
- "saves, e.g. the difference between estimate_workload_carbon in the two regions).")
136
+ # Say up front that the question 'does it backfire?' is NOT answered yet, and what is missing.
137
+ warning = ("NO NET VERDICT: you did not pass carbon_saving_kg_co2_from_move, so this does NOT say "
138
+ "whether the move backfires on carbon. Ask the user for it, or compute it as the difference "
139
+ "between estimate_workload_carbon in the two regions, and call again. " + warning)
136
140
 
141
+ if verdict is None:
142
+ summary = ("NO NET VERDICT: carbon_saving_kg_co2_from_move was not provided, so this does not say "
143
+ f"whether the move backfires. Estimated {cost_txt.replace('roughly ', '')}; "
144
+ f"transfer ~{transfer_kg:.1f} kg CO2/mo. Supply the saving and call again.")
145
+ else:
146
+ summary = f"{verdict} Estimated {cost_txt.replace('roughly ', '')}."
137
147
  if severity == "high":
138
148
  rec = ("High exposure. Co-locate compute with the data, or move the data to the greener "
139
149
  "region too. If you can't, confirm the carbon/cost saving beats this egress penalty "
@@ -160,6 +170,6 @@ async def data_locality_check(
160
170
  est_monthly_transfer_kg_co2=transfer_kg,
161
171
  transfer_carbon_source=reading.source, transfer_carbon_quality=reading.quality,
162
172
  carbon_saving_kg_co2_from_move=carbon_saving_kg_co2_from_move,
163
- net_carbon_verdict=verdict, residency_note=residency_note,
173
+ summary=summary, net_carbon_verdict=verdict, verdict_status=verdict_status, residency_note=residency_note,
164
174
  warning=warning, recommendation=rec, limitations=LIMITATIONS,
165
175
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: greenpick
3
- Version: 0.5.0
3
+ Version: 0.5.2
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
@@ -1882,3 +1882,30 @@ def test_tool_greenest_regions_filters_and_errors():
1882
1882
  assert r["ranking"] and all(x["provider"] == "aws" for x in r["ranking"])
1883
1883
  assert any(x["quality"] == "annual_average" for x in _tool("greenest_regions_on_average")(month=1)["ranking"])
1884
1884
  assert "error" in _tool("greenest_regions_on_average")(month=13)
1885
+
1886
+
1887
+ # --- 0.5.1: a missing carbon saving is called out, not silently skipped ---
1888
+ @pytest.mark.asyncio
1889
+ async def test_locality_without_saving_says_no_verdict():
1890
+ from greenpick.services import locality_service as ls
1891
+ base = dict(compute_provider="aws", compute_region="eu-north-1",
1892
+ data_provider="aws", data_region="us-east-1", monthly_cross_boundary_gb=100)
1893
+ no = await ls.data_locality_check(MockCarbonRepository(), **base)
1894
+ assert no.net_carbon_verdict is None and no.verdict_status == "not_evaluated"
1895
+ assert no.warning.startswith("NO NET VERDICT") and "carbon_saving_kg_co2_from_move" in no.warning
1896
+ yes = await ls.data_locality_check(MockCarbonRepository(), carbon_saving_kg_co2_from_move=40.0, **base)
1897
+ assert yes.verdict_status == "evaluated" and not yes.warning.startswith("NO NET VERDICT")
1898
+
1899
+
1900
+ # --- 0.5.2: the one-line summary is the FIRST key, so partial printouts still show it ---
1901
+ @pytest.mark.asyncio
1902
+ async def test_locality_summary_is_first_key_and_flags_missing_verdict():
1903
+ from greenpick.services import locality_service as ls
1904
+ base = dict(compute_provider="aws", compute_region="eu-north-1",
1905
+ data_provider="aws", data_region="us-east-1", monthly_cross_boundary_gb=100)
1906
+ no = (await ls.data_locality_check(MockCarbonRepository(), **base)).model_dump()
1907
+ assert next(iter(no)) == "summary" and no["summary"].startswith("NO NET VERDICT")
1908
+ yes = (await ls.data_locality_check(MockCarbonRepository(), carbon_saving_kg_co2_from_move=40.0, **base)).model_dump()
1909
+ assert "net win" in yes["summary"].lower() and not yes["summary"].startswith("NO NET VERDICT")
1910
+ same = (await ls.data_locality_check(MockCarbonRepository(), "aws", "us-east-1", "aws", "us-east-1", 100)).model_dump()
1911
+ assert next(iter(same)) == "summary" and same["summary"]
File without changes
File without changes
File without changes