1 Commits

Author SHA1 Message Date
claude 65509a27d6 feat: nutrient_cost tool — cheapest fertilizer per lb of N/P/K
CI / test (pull_request) Successful in 18s
CI / build-push (pull_request) Has been skipped
New agbids__nutrient_cost(geo) tool: wraps ag-monitor's /api/data/nutrient-cost
and formats a per-nutrient value comparison for the advisor — cheapest source of
N / P2O5 / K2O + a ranked $/lb table. This is what answers "which fertilizer is
the best nitrogen value per dollar?" (input_cost_trend is $/ton only). UAN grade
shown as assumed 32-0-0.

client.nutrient_cost + fmt.fmt_nutrient_cost (+ _per_lb). 42 tests pass.

For planning #90.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 15:52:33 -04:00
4 changed files with 5 additions and 43 deletions
+1 -1
View File
@@ -83,7 +83,7 @@ auto-updates it every 5 minutes.
| `price_series(commodity, geo?, start_year?, end_year?)` | Raw monthly price-received series for charting |
| `input_cost_trend(item, geo?, years?)` | Real input cost + change. `item` = `diesel` (U.S. `$/gal`, weekly, EIA) or a fertilizer `$/ton` (`urea`/`uan`/`anhydrous`/`dap`/`map`/`potash`, monthly, USDA AgTransport); `geo` selects the fertilizer region (default `Cornbelt`) |
| `input_cost_series(item, geo?)` | Raw historical series for an input cost (diesel `$/gal` or fertilizer `$/ton`, region-selectable) |
| `latest_prices(commodity?, source?, delivery?, kind?, zip?, lat?, lng?, radius_miles?, include_expired?)` | Live snapshot table; every filter optional (`kind` = `grain`/`fertilizer`). Current + future delivery months only by default; pass `include_expired=true` for past months (use only on an explicit historical request). Location filter (zip **or** lat/lng + `radius_miles`) keeps nearby sources, nearest-first, with distance |
| `latest_prices(commodity?, source?, delivery?, kind?, zip?, lat?, lng?, radius_miles?)` | Live snapshot table; every filter optional (`kind` = `grain`/`fertilizer`). Location filter (zip **or** lat/lng + `radius_miles`) keeps nearby sources, nearest-first, with distance |
| `price_history(commodity?, source?, delivery?, days?)` | Time series per (elevator, crop, delivery); **every filter optional** — omit `commodity` to span all crops. Shows bid + basis trend + futures |
| `basis_movement(commodity?, source?, delivery?, days?)` | Aggregated basis trend, one headline line per crop (the cheap overview) |
| `basis_detail(commodity?, source?, delivery?, days?)` | Per-(elevator, crop, delivery) basis first→last drill-down |
+2 -6
View File
@@ -56,14 +56,10 @@ 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,
zip: str | None = None, lat: float | None = None,
lng: float | None = None, radius_miles: float | None = None,
include_expired: bool = False) -> dict:
# Only send include_expired when True — _get drops None, so the default
# call keeps its existing query string (current + future months only).
lng: float | None = None, radius_miles: float | None = None) -> dict:
return _get("/api/data/latest",
commodity=commodity, source=source, delivery=delivery, kind=kind,
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles,
include_expired=True if include_expired else None)
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles)
def history(commodity: str | None = None, source_id: int | None = None,
+2 -12
View File
@@ -176,22 +176,13 @@ def latest_prices(
Field(description="Search radius in miles around the location (default 50). "
"Ignored unless a ZIP or lat/lng is given."),
] = None,
include_expired: Annotated[
bool,
Field(description="Include past/expired delivery months. Default off — only "
"the current and future months are shown. Set true only when "
"the farmer explicitly asks for historical/past delivery data."),
] = False,
) -> 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. Pass a `zip` (or `lat`+`lng`) with
optional `radius_miles` to keep only elevators near the farmer, sorted
nearest-first with the distance shown.
By default this shows current + future delivery months only; expired
months are rolled off. Set `include_expired=true` for a historical view."""
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,
zip=zip or "", radius=radius_miles or 0):
@@ -199,8 +190,7 @@ def latest_prices(
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,
include_expired=include_expired)
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles)
return fmt.fmt_latest(payload)
-24
View File
@@ -71,30 +71,6 @@ def test_get_drops_none_params(monkeypatch):
assert captured["params"] == {"commodity": "corn"}
def test_latest_include_expired_passthrough(monkeypatch):
client = _reload_client(monkeypatch)
captured = {}
class FakeResp:
status_code = 200
text = ""
def json(self): return {"count": 0, "rows": []}
def fake_get(url, params=None, timeout=None, headers=None):
captured["params"] = dict(params or {})
return FakeResp()
monkeypatch.setattr(client.httpx, "get", fake_get)
# Default: not sent, so the API applies its current+future filter.
client.latest(commodity="corn")
assert "include_expired" not in captured["params"]
# Opt in: forwarded so the API returns expired months too.
client.latest(commodity="corn", include_expired=True)
assert captured["params"] == {"commodity": "corn", "include_expired": True}
def test_futures_endpoint_params(monkeypatch):
client = _reload_client(monkeypatch)
captured = {}