3 Commits

Author SHA1 Message Date
claude 3bd9bf38ee docs(README): document include_expired on the latest snapshot
CI / test (pull_request) Successful in 17s
CI / build-push (pull_request) Has been skipped
Follow-up to the expired-month filter — the API/tool table didn't mention
the new default (current + future only) or the include_expired opt-in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 22:36:17 -04:00
justin 831c3e19de latest_prices: include_expired passthrough for historical data (#2)
CI / test (push) Successful in 19s
CI / build-push (push) Successful in 5s
2026-06-08 19:45:03 -04:00
claude bb4219da87 feat: nutrient_cost tool — cheapest fertilizer per lb of N/P/K (#1)
CI / test (push) Successful in 18s
CI / build-push (push) Successful in 5s
Co-authored-by: claude <claude@jpaul.io>
Co-committed-by: claude <claude@jpaul.io>
2026-06-04 15:58:59 -04:00
6 changed files with 166 additions and 5 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 | | `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_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) | | `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?)` | Live snapshot table; every filter optional (`kind` = `grain`/`fertilizer`). 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?, 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 |
| `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 | | `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_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 | | `basis_detail(commodity?, source?, delivery?, days?)` | Per-(elevator, crop, delivery) basis first→last drill-down |
+10 -2
View File
@@ -56,10 +56,14 @@ def _get(path: str, **params: Any) -> dict | list:
def latest(commodity: str | None = None, source: str | None = None, def latest(commodity: str | None = None, source: str | None = None,
delivery: str | None = None, kind: str | None = None, delivery: str | None = None, kind: str | None = None,
zip: str | None = None, lat: float | None = None, zip: str | None = None, lat: float | None = None,
lng: float | None = None, radius_miles: float | None = None) -> dict: 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).
return _get("/api/data/latest", 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) zip=zip, lat=lat, lng=lng, radius_miles=radius_miles,
include_expired=True if include_expired else None)
def history(commodity: str | None = None, source_id: int | None = None, def history(commodity: str | None = None, source_id: int | None = None,
@@ -97,6 +101,10 @@ def input_cost_geographies(item: str) -> dict:
return _get("/api/data/input-cost-geographies", item=item) return _get("/api/data/input-cost-geographies", item=item)
def nutrient_cost(geo: str | None = None) -> dict:
return _get("/api/data/nutrient-cost", geo=geo)
def futures(commodity: str, delivery: str | None = None) -> dict: def futures(commodity: str, delivery: str | None = None) -> dict:
return _get("/api/data/futures", commodity=commodity, delivery=delivery) return _get("/api/data/futures", commodity=commodity, delivery=delivery)
+57
View File
@@ -22,6 +22,12 @@ def _ton(cents: Optional[int]) -> str:
return f"${cents / 100:.2f}" return f"${cents / 100:.2f}"
def _per_lb(cents: Optional[int]) -> str:
if cents is None:
return ""
return f"${cents / 100:.2f}"
def _basis(cents: Optional[int]) -> str: def _basis(cents: Optional[int]) -> str:
if cents is None: if cents is None:
return "" return ""
@@ -207,6 +213,57 @@ def fmt_price_series(payload: dict, max_points: int = 60) -> str:
return "\n".join(head + body) + "\n" return "\n".join(head + body) + "\n"
_NUTRIENT_LABEL = {"n": "Nitrogen (N)", "p2o5": "Phosphate (P₂O₅)", "k2o": "Potash (K₂O)"}
def fmt_nutrient_cost(payload: dict) -> str:
"""Cheapest fertilizer per pound of N / P2O5 / K2O for a region."""
geo = payload.get("geo") or "Cornbelt"
src = payload.get("source") or "USDA AgTransport"
products = payload.get("products") or []
cheapest = payload.get("cheapest") or {}
if not products:
return f"### Fertilizer value per nutrient — {geo}\n\nNo data on file.\n"
by_item = {p["item"]: p for p in products}
lines = [f"### Fertilizer value per pound of nutrient — {geo}", ""]
for nut in ("n", "p2o5", "k2o"):
it = cheapest.get(nut)
prod = by_item.get(it or "")
if prod is not None:
c = (prod.get("cost_per_lb") or {}).get(nut)
lines.append(
f"- **Cheapest {_NUTRIENT_LABEL[nut]}:** "
f"{prod.get('label') or it} at {_per_lb(c)}/lb"
)
lines += ["", "| Product | Grade | $/ton | $/lb N | $/lb P₂O₅ | $/lb K₂O |",
"|---|---|---:|---:|---:|---:|"]
def _nkey(p: dict):
v = (p.get("cost_per_lb") or {}).get("n")
return (v is None, v if v is not None else 0)
for p in sorted(products, key=_nkey): # cheapest N first, blanks last
a = p.get("analysis") or {}
cpl = p.get("cost_per_lb") or {}
grade = (a.get("grade") or "") + ("*" if a.get("grade_assumed") else "")
lines.append(
f"| {p.get('label') or p['item']} | {grade} | {_ton(p.get('price_cents_per_ton'))} | "
f"{_per_lb(cpl.get('n'))} | {_per_lb(cpl.get('p2o5'))} | {_per_lb(cpl.get('k2o'))} |"
)
period = next((p.get("period") for p in products if p.get("period")), None)
assumed = any((p.get("analysis") or {}).get("grade_assumed") for p in products)
foot = f"_Source: {src}"
if period:
foot += f" · as of {_ym(period)}"
foot += " · $/lb = $/ton ÷ (analysis% × 2000 lb)"
if assumed:
foot += " · *UAN grade assumed 32-0-0"
lines.append("\n" + foot + "_")
return "\n".join(lines) + "\n"
# ---------- futures quote + change ---------- # ---------- futures quote + change ----------
+33 -2
View File
@@ -176,13 +176,22 @@ def latest_prices(
Field(description="Search radius in miles around the location (default 50). " Field(description="Search radius in miles around the location (default 50). "
"Ignored unless a ZIP or lat/lng is given."), "Ignored unless a ZIP or lat/lng is given."),
] = None, ] = 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: ) -> str:
"""Snapshot of the latest scraped bid per (source, commodity, delivery). """Snapshot of the latest scraped bid per (source, commodity, delivery).
Every filter is optional and AND'd together — pivot by elevator, crop, 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 delivery, or kind in any combination. Pass a `zip` (or `lat`+`lng`) with
optional `radius_miles` to keep only elevators near the farmer, sorted optional `radius_miles` to keep only elevators near the farmer, sorted
nearest-first with the distance shown.""" 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."""
cm = commodity.strip().lower() if commodity else None cm = commodity.strip().lower() if commodity else None
with track("latest_prices", 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): zip=zip or "", radius=radius_miles or 0):
@@ -190,7 +199,8 @@ def latest_prices(
if err: if err:
return err return err
payload = client.latest(commodity=cm, source=source, delivery=delivery, kind=kind, payload = client.latest(commodity=cm, source=source, delivery=delivery, kind=kind,
zip=zip, lat=lat, lng=lng, radius_miles=radius_miles) zip=zip, lat=lat, lng=lng, radius_miles=radius_miles,
include_expired=include_expired)
return fmt.fmt_latest(payload) return fmt.fmt_latest(payload)
@@ -411,6 +421,27 @@ def input_cost_series(
return fmt.fmt_price_series(client.input_cost_series(item=it, geo=g)) return fmt.fmt_price_series(client.input_cost_series(item=it, geo=g))
@mcp.tool()
def nutrient_cost(
geo: Annotated[
str | None,
Field(description="Fertilizer region (default 'Cornbelt'). Same regions as "
"input_cost_trend (e.g. 'U.S. Gulf NOLA', 'Northern Plains')."),
] = None,
) -> str:
"""Cheapest fertilizer per POUND of nutrient — "what's the best value for N (or P/K)?".
Converts each fertilizer's regional $/ton (USDA AgTransport, latest month) into
$/lb of N, P2O5, and K2O from its grade, ranks them, and names the cheapest
source of each nutrient (anhydrous usually wins on N; DAP/MAP are phosphate
buys; potash is the K source). Use THIS — not input_cost_trend ($/ton only) —
whenever the grower asks which fertilizer is the best buy / best nitrogen value.
UAN grade is assumed 32-0-0."""
g = geo.strip() if geo else None
with track("nutrient_cost", geo=g or ""):
return fmt.fmt_nutrient_cost(client.nutrient_cost(geo=g))
@mcp.tool() @mcp.tool()
def list_sources() -> str: def list_sources() -> str:
"""All active scrapers + their last-success timestamps and any pending failures.""" """All active scrapers + their last-success timestamps and any pending failures."""
+24
View File
@@ -71,6 +71,30 @@ def test_get_drops_none_params(monkeypatch):
assert captured["params"] == {"commodity": "corn"} 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): def test_futures_endpoint_params(monkeypatch):
client = _reload_client(monkeypatch) client = _reload_client(monkeypatch)
captured = {} captured = {}
+41
View File
@@ -390,3 +390,44 @@ def test_fmt_summary_includes_best_for_today():
assert "Mercer Landmark — St Henry" in out assert "Mercer Landmark — St Henry" in out
assert "$4.5800" in out # corn last assert "$4.5800" in out # corn last
assert "No current-month local bid posted" in out # soy fallback assert "No current-month local bid posted" in out # soy fallback
def test_fmt_nutrient_cost():
payload = {
"geo": "Cornbelt",
"source": "USDA AgTransport",
"products": [
{"item": "anhydrous", "label": "anhydrous ammonia", "unit": "$/ton",
"geo": "Cornbelt", "period": "2026-03-01", "price_cents_per_ton": 88062,
"analysis": {"grade": "82-0-0", "grade_assumed": False},
"cost_per_lb": {"n": 54, "p2o5": None, "k2o": None}},
{"item": "urea", "label": "urea", "unit": "$/ton", "geo": "Cornbelt",
"period": "2026-03-01", "price_cents_per_ton": 69812,
"analysis": {"grade": "46-0-0", "grade_assumed": False},
"cost_per_lb": {"n": 76, "p2o5": None, "k2o": None}},
{"item": "uan", "label": "UAN (28-32%)", "unit": "$/ton", "geo": "Cornbelt",
"period": "2026-03-01", "price_cents_per_ton": 47612,
"analysis": {"grade": "32-0-0", "grade_assumed": True},
"cost_per_lb": {"n": 74, "p2o5": None, "k2o": None}},
{"item": "potash", "label": "potash (0-0-60)", "unit": "$/ton",
"geo": "Cornbelt", "period": "2026-03-01", "price_cents_per_ton": 35938,
"analysis": {"grade": "0-0-60", "grade_assumed": False},
"cost_per_lb": {"n": None, "p2o5": None, "k2o": 30}},
],
"cheapest": {"n": "anhydrous", "p2o5": None, "k2o": "potash"},
}
out = fmt.fmt_nutrient_cost(payload)
assert "Cornbelt" in out
assert "Cheapest Nitrogen (N):** anhydrous ammonia at $0.54/lb" in out
assert "Cheapest Potash (K₂O):** potash (0-0-60) at $0.30/lb" in out
# anhydrous (cheapest N) sorts above urea in the table
assert out.index("anhydrous ammonia |") < out.index("| urea |")
# UAN grade-assumed marker + footnote
assert "32-0-0*" in out
assert "UAN grade assumed 32-0-0" in out
# potash supplies no N -> em-dash in the N column
assert "" in out
def test_fmt_nutrient_cost_empty():
assert "No data on file" in fmt.fmt_nutrient_cost({"geo": "Cornbelt", "products": []})