Location params on best_local_bid / latest_prices (zip / GPS + radius)
CI / test (push) Successful in 17s
CI / build-push (push) Successful in 6s

Thread zip/lat/lng/radius_miles through the client and both tools; friendly
guard for the zip-XOR-gps rule. Formatters surface distance, the searched
center, and the nearest-source hint when nothing is in range.

- client: best()/latest() take zip/lat/lng/radius_miles
- server: location params + docstrings (note Ohio-concentrated coverage)
- format: distance column + center/nearest rendering
- README + CHANGELOG + advisor prompt library updated
- tests: location formatting cases

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 07:51:54 -04:00
parent ae15aa5c3c
commit fb50c103d3
7 changed files with 245 additions and 31 deletions
+9 -4
View File
@@ -54,9 +54,12 @@ def _get(path: str, **params: Any) -> dict | list:
def latest(commodity: str | None = None, source: str | None = None,
delivery: str | None = None, kind: str | None = None) -> dict:
delivery: str | None = None, kind: str | None = None,
zip: str | None = None, lat: float | None = None,
lng: float | None = None, radius_miles: float | None = None) -> dict:
return _get("/api/data/latest",
commodity=commodity, source=source, delivery=delivery, kind=kind)
commodity=commodity, source=source, delivery=delivery, kind=kind,
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles)
def history(commodity: str | None = None, source_id: int | None = None,
@@ -66,8 +69,10 @@ def history(commodity: str | None = None, source_id: int | None = None,
delivery=delivery, days=days)
def best(commodity: str) -> dict:
return _get("/api/data/best", commodity=commodity)
def best(commodity: str, zip: str | None = None, lat: float | None = None,
lng: float | None = None, radius_miles: float | None = None) -> dict:
return _get("/api/data/best", commodity=commodity,
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles)
def price_trend(commodity: str, geo: str = "US", years: int = 10) -> dict:
+62 -16
View File
@@ -73,21 +73,56 @@ def _first_last(points: list[dict], field: str):
# ---------- best_local_bid ----------
def _loc_suffix(d: dict) -> str:
"""' (City, ST)' when a row/result carries city/state, else ''."""
city, state = d.get("city"), d.get("state")
if city and state:
return f" ({city}, {state})"
return f" ({state})" if state else ""
def _mi(d) -> str:
return "" if d is None else f"{d:.1f} mi"
def _where(center: dict) -> str:
"""Human label for a resolved center point."""
if center.get("source") == "zip":
return f"ZIP {center.get('zip')}"
near = f" (near {center.get('zip')})" if center.get("zip") else ""
return f"{center['lat']:.4f}, {center['lng']:.4f}{near}"
def fmt_best(commodity: str, payload: dict) -> str:
best = payload.get("best")
today = payload.get("today")
center = payload.get("center")
radius = payload.get("radius_miles")
scope = f" within {radius:.0f} mi of {_where(center)}" if center else ""
head = f"### Best place to sell {commodity} today ({today})\n\n"
if not best:
return (
f"### Best place to sell {commodity} today ({today})\n\n"
f"No current-month {commodity} bids posted across the tracked sources.\n"
)
return (
f"### Best place to sell {commodity} today ({today})\n\n"
f"**{best['source_name']}** — delivery **{best['delivery']}** — "
f"bid **{_bu(best['bid_cents'])}/bu** (basis {_basis(best.get('basis_cents'))}, "
f"futures {best.get('futures_contract') or ''})\n\n"
f"_Fetched {best.get('fetched_at') or '?'}_\n"
if center:
out = head + f"No current-month {commodity} bids{scope}.\n"
near = payload.get("nearest")
if near:
out += (f"\nNearest tracked elevator: **{near['source_name']}**"
f"{_loc_suffix(near)} — about **{_mi(near.get('distance_miles'))}** "
f"away, outside the {radius:.0f} mi radius.\n")
return out
return head + f"No current-month {commodity} bids posted across the tracked sources.\n"
dist = best.get("distance_miles")
dist_txt = f" — **{_mi(dist)}** away" if dist is not None else ""
out = (
head
+ f"**{best['source_name']}**{_loc_suffix(best)}{dist_txt} — delivery "
f"**{best['delivery']}** — bid **{_bu(best['bid_cents'])}/bu** "
f"(basis {_basis(best.get('basis_cents'))}, "
f"futures {best.get('futures_contract') or ''})\n"
)
if center:
out += f"\n_Searched{scope}._\n"
out += f"\n_Fetched {best.get('fetched_at') or '?'}_\n"
return out
# ---------- reference price trends (USDA NASS / EIA) ----------
@@ -235,20 +270,31 @@ def fmt_inputs(payload: dict) -> str:
def fmt_latest(payload: dict) -> str:
rows = payload.get("rows") or []
center = payload.get("center")
radius = payload.get("radius_miles")
if not rows:
return "### Latest prices\n\nNo rows match those filters.\n"
head = "### Latest prices\n\n"
if center:
return head + f"No sources within {radius:.0f} mi of {_where(center)}.\n"
return head + "No rows match those filters.\n"
title = "### Latest prices"
if center:
title += f" — within {radius:.0f} mi of {_where(center)}"
dist_col = " Distance |" if center else ""
dist_sep = " ---: |" if center else ""
lines = [
"### Latest prices", "",
"| Source | Commodity | Delivery | Bid | Basis | Futures | Fetched |",
"|---|---|---|---:|---:|---|---|",
title, "",
f"| Source | Commodity | Delivery | Bid | Basis | Futures |{dist_col} Fetched |",
f"|---|---|---|---:|---:|---|{dist_sep}---|",
]
for r in rows:
unit_fmt = _ton if r.get("commodity_kind") == "fertilizer" else _bu
dist_cell = f" {_mi(r.get('distance_miles'))} |" if center else ""
lines.append(
f"| {r['source_name']} | {r.get('display_name') or r['commodity']} | "
f"{r['delivery']} | {unit_fmt(r.get('bid_cents'))} | "
f"{_basis(r.get('basis_cents'))} | {r.get('futures_contract') or ''} | "
f"{r.get('fetched_at') or '?'} |"
f"{_basis(r.get('basis_cents'))} | {r.get('futures_contract') or ''} |"
f"{dist_cell} {r.get('fetched_at') or '?'} |"
)
return "\n".join(lines) + "\n"
+57 -6
View File
@@ -48,19 +48,50 @@ VALID_INPUT = {"lime", "map", "potash"}
# ============================================================================
def _location_error(zip: str | None, lat: float | None, lng: float | None) -> str | None:
"""Friendly guard for the (zip XOR gps) rule before hitting the API."""
if zip and (lat is not None or lng is not None):
return "Pass either `zip` or `lat`+`lng`, not both."
if (lat is None) != (lng is None):
return "GPS needs both `lat` and `lng`."
return None
@mcp.tool()
def best_local_bid(
commodity: Annotated[
str, Field(description="Grain to look up: 'corn', 'soy' (soybeans), or 'wheat'.")
],
zip: Annotated[
str | None,
Field(description="Your 5-digit ZIP — limits the search to nearby elevators."),
] = None,
lat: Annotated[
float | None, Field(description="Your latitude (use with `lng` instead of a ZIP).")
] = None,
lng: Annotated[float | None, Field(description="Your longitude (use with `lat`).")] = None,
radius_miles: Annotated[
float | None,
Field(description="Search radius in miles around the location (default 50). "
"Ignored unless a ZIP or lat/lng is given."),
] = None,
) -> str:
"""Return the highest local bid for *this calendar month's* delivery for
the given grain. This is the "where should I haul today" answer."""
the given grain the "where should I haul today" answer.
Pass a `zip` (or `lat`+`lng`) with optional `radius_miles` to restrict to
elevators near the farmer; results then show the distance to each. Without a
location it considers every scraped elevator. If nothing is in range, it
reports the nearest source and its distance (a coverage gap, not an error).
Note: cash-bid coverage is currently concentrated in Ohio."""
commodity = commodity.strip().lower()
with track("best_local_bid", commodity=commodity):
with track("best_local_bid", commodity=commodity, zip=zip or "", radius=radius_miles or 0):
if commodity not in VALID_GRAIN:
return f"`commodity` must be one of: {sorted(VALID_GRAIN)}"
payload = client.best(commodity)
err = _location_error(zip, lat, lng)
if err:
return err
payload = client.best(commodity, zip=zip, lat=lat, lng=lng, radius_miles=radius_miles)
return fmt.fmt_best(commodity, payload)
@@ -132,14 +163,34 @@ def latest_prices(
str | None,
Field(description="Filter to one commodity kind: 'grain' or 'fertilizer'. Omit for all."),
] = None,
zip: Annotated[
str | None,
Field(description="Your 5-digit ZIP — keep only nearby sources, nearest first."),
] = None,
lat: Annotated[
float | None, Field(description="Your latitude (use with `lng` instead of a ZIP).")
] = None,
lng: Annotated[float | None, Field(description="Your longitude (use with `lat`).")] = None,
radius_miles: Annotated[
float | None,
Field(description="Search radius in miles around the location (default 50). "
"Ignored unless a ZIP or lat/lng is given."),
] = None,
) -> str:
"""Snapshot of the latest scraped bid per (source, commodity, delivery).
Every filter is optional and AND'd together — pivot by elevator, crop,
delivery, or kind in any combination."""
delivery, or kind in any combination. Pass a `zip` (or `lat`+`lng`) with
optional `radius_miles` to keep only elevators near the farmer, sorted
nearest-first with the distance shown."""
cm = commodity.strip().lower() if commodity else None
with track("latest_prices", commodity=cm, source=source, delivery=delivery, kind=kind):
payload = client.latest(commodity=cm, source=source, delivery=delivery, kind=kind)
with track("latest_prices", commodity=cm, source=source, delivery=delivery, kind=kind,
zip=zip or "", radius=radius_miles or 0):
err = _location_error(zip, lat, lng)
if err:
return err
payload = client.latest(commodity=cm, source=source, delivery=delivery, kind=kind,
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles)
return fmt.fmt_latest(payload)