Add fertilizer to input-cost tools (regional $/ton, USDA AgTransport)
input_cost_trend/input_cost_series now accept six fertilizers (urea, uan, anhydrous, dap, map, potash) alongside diesel, with an optional `geo` region (default Cornbelt). Real $/ton + MoM/YoY change + seasonal context. - client: pass geo through; add input_cost_geographies - server: expand VALID_INPUTS; geo param + docstrings - format already unit-aware ($/ton) and geo-aware - README tools table now lists the reference/trend + input-cost tools - CHANGELOG: regional fertilizer input-cost release notes - tests: fertilizer $/ton + region formatting Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,46 @@
|
||||
Notes for clients/agents that consume the ag-bids MCP tools and the underlying
|
||||
`ag-monitor` `/api/data/*` HTTP API. Newest first.
|
||||
|
||||
## 2026-05-30 — Regional fertilizer input costs (real $/ton + change)
|
||||
|
||||
Real U.S. **regional retail fertilizer prices** now feed the input-cost tools,
|
||||
so the advisor can reason about fertilizer the same way it does diesel and grain
|
||||
— actual dollars and the move, not an index. Source: **USDA AgTransport**
|
||||
(monthly, `$/ton`, history back to 2023).
|
||||
|
||||
### Changed MCP tools (fertilizer added to the existing input-cost tools)
|
||||
|
||||
- **`input_cost_trend(item, geo?, years=10)`** — `item` now accepts six
|
||||
fertilizers in addition to `diesel`: **`urea`, `uan`, `anhydrous`** (anhydrous
|
||||
ammonia), **`dap`, `map`, `potash`**. Returns the latest real `$/ton`,
|
||||
month-over-month and year-over-year change ($ and %), and seasonal context.
|
||||
- New optional **`geo`** = AgTransport region; **defaults to `Cornbelt`**.
|
||||
Other regions: `U.S. Gulf NOLA`, `Northern Plains`, `Southern Plains`,
|
||||
`Southeast`, `Northeast`, `California`, `Pacific Northwest`, `South Central`,
|
||||
`Central Florida`, `Tampa` (not every product is published for every region).
|
||||
- `diesel` is unchanged — national U.S. `$/gal`, weekly; `geo` is ignored for it.
|
||||
- **`input_cost_series(item, geo?)`** — raw monthly `$/ton` series for any
|
||||
fertilizer (or the diesel `$/gal` weekly series), region-selectable.
|
||||
|
||||
Seasonal percentile for fertilizer is computed over a short history (2023+), so
|
||||
it deepens over time; the price and YoY/MoM change are solid now.
|
||||
|
||||
### API
|
||||
|
||||
- `GET /api/data/input-cost-trend?item=&geo=&years=` — `geo` added.
|
||||
- `GET /api/data/input-cost-series?item=&geo=` — `geo` added.
|
||||
- `GET /api/data/input-cost-geographies?item=` — **new**: lists the regions a
|
||||
given input has data for (plus its `default_geo`).
|
||||
|
||||
### Example questions → tool calls
|
||||
|
||||
| Ask | Call |
|
||||
|---|---|
|
||||
| Cornbelt urea price and how it's moved | `input_cost_trend(item="urea")` |
|
||||
| Anhydrous ammonia in the Southern Plains | `input_cost_trend(item="anhydrous", geo="Southern Plains")` |
|
||||
| Potash $/ton history | `input_cost_series(item="potash")` |
|
||||
| Which regions have DAP prices | (API) `GET /api/data/input-cost-geographies?item=dap` |
|
||||
|
||||
## 2026-05-30 — Grain price-received trends (real $ + change + seasonal)
|
||||
|
||||
New national/seasonal reference layer (USDA NASS, corn back to 1908) — the
|
||||
|
||||
@@ -25,8 +25,9 @@ ag-bids-mcp ── X-API-Key ─► https://agbids.paul.farm/api/data/*
|
||||
```
|
||||
|
||||
Endpoints used: `/api/data/latest`, `/history`, `/futures`, `/best`, `/inputs`,
|
||||
`/sources`, `/deliveries`. See the [ag-monitor source](https://git.jpaul.io/justin/ag-bids)
|
||||
for the contract.
|
||||
`/sources`, `/deliveries`, `/price-trend`, `/price-series`, `/price-geographies`,
|
||||
`/input-cost-trend`, `/input-cost-series`, `/input-cost-geographies`. See the
|
||||
[ag-monitor source](https://git.jpaul.io/justin/ag-bids) for the contract.
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -77,7 +78,11 @@ auto-updates it every 5 minutes.
|
||||
| `best_local_bid(commodity)` | Where to sell `corn`, `soy`, or `wheat` for this month's delivery — markdown one-liner + table |
|
||||
| `futures_quote(commodity, delivery?)` | CBOT futures price + change since open + change on the day. With a delivery month it resolves the listed contract; without it, the continuous nearby |
|
||||
| `current_lime_price()` | Latest lime quotes across all manual-entry sources |
|
||||
| `current_input_price(product?)` | MAP / Potash / Lime — all three or one |
|
||||
| `current_input_price(product?)` | MAP / Potash / Lime — all three or one (local DTN dealer feed) |
|
||||
| `price_trend(commodity, geo?, years?)` | USDA NASS monthly price *received* ($/bu) + MoM/YoY change + seasonal context. `geo` = `US` or a state (corn back to 1908) |
|
||||
| `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?)` | Live snapshot table; every filter optional (`kind` = `grain`/`fertilizer`) |
|
||||
| `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) |
|
||||
|
||||
@@ -80,12 +80,16 @@ def price_series(commodity: str, geo: str = "US",
|
||||
start_year=start_year, end_year=end_year)
|
||||
|
||||
|
||||
def input_cost_trend(item: str, years: int = 10) -> dict:
|
||||
return _get("/api/data/input-cost-trend", item=item, years=years)
|
||||
def input_cost_trend(item: str, years: int = 10, geo: str | None = None) -> dict:
|
||||
return _get("/api/data/input-cost-trend", item=item, years=years, geo=geo)
|
||||
|
||||
|
||||
def input_cost_series(item: str) -> dict:
|
||||
return _get("/api/data/input-cost-series", item=item)
|
||||
def input_cost_series(item: str, geo: str | None = None) -> dict:
|
||||
return _get("/api/data/input-cost-series", item=item, geo=geo)
|
||||
|
||||
|
||||
def input_cost_geographies(item: str) -> dict:
|
||||
return _get("/api/data/input-cost-geographies", item=item)
|
||||
|
||||
|
||||
def futures(commodity: str, delivery: str | None = None) -> dict:
|
||||
|
||||
+33
-12
@@ -303,40 +303,61 @@ def price_series(
|
||||
return fmt.fmt_price_series(payload)
|
||||
|
||||
|
||||
VALID_INPUTS = {"diesel"}
|
||||
# Fuel is national (US); fertilizer is regional ($/ton, USDA AgTransport).
|
||||
VALID_INPUTS = {"diesel", "urea", "uan", "anhydrous", "dap", "map", "potash"}
|
||||
_FERT_INPUTS = {"urea", "uan", "anhydrous", "dap", "map", "potash"}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def input_cost_trend(
|
||||
item: Annotated[
|
||||
str, Field(description="Input to price. Currently: 'diesel' (U.S. retail $/gal).")
|
||||
str,
|
||||
Field(description="Input to price: 'diesel' (U.S. retail $/gal) or a fertilizer "
|
||||
"($/ton) — 'urea', 'uan', 'anhydrous', 'dap', 'map', 'potash'."),
|
||||
],
|
||||
geo: Annotated[
|
||||
str | None,
|
||||
Field(description="Fertilizer region (default 'Cornbelt'). Other regions: "
|
||||
"'U.S. Gulf NOLA', 'Northern Plains', 'Southern Plains', "
|
||||
"'Southeast', 'Northeast', 'California', 'Pacific Northwest', "
|
||||
"'South Central', 'Central Florida', 'Tampa'. Ignored for diesel (US)."),
|
||||
] = None,
|
||||
years: Annotated[
|
||||
int, Field(ge=1, le=120, description="Baseline window for the seasonal normal/percentile."),
|
||||
] = 10,
|
||||
) -> str:
|
||||
"""Real input cost with the change — e.g. U.S. retail diesel ($/gal).
|
||||
"""Real input cost with the change — retail diesel ($/gal) or fertilizer ($/ton).
|
||||
|
||||
Latest real price + week-over-week and year-over-year moves + seasonal
|
||||
percentile/range. Fills the input-cost side for the advisor (fuel). For
|
||||
current fertilizer $/ton use current_input_price."""
|
||||
Latest real price + period-over-period and year-over-year moves + seasonal
|
||||
percentile/range. Diesel is U.S. weekly (back to 1994); fertilizer is monthly
|
||||
regional (USDA AgTransport, Cornbelt default, back to 2023). This is the
|
||||
input-cost side for the advisor. For a one-off current fertilizer quote from a
|
||||
local dealer feed, current_input_price (DTN) still applies."""
|
||||
it = item.strip().lower()
|
||||
with track("input_cost_trend", item=it, years=years):
|
||||
g = geo.strip() if geo else None
|
||||
with track("input_cost_trend", item=it, geo=g or "", years=years):
|
||||
if it not in VALID_INPUTS:
|
||||
return f"`item` must be one of: {sorted(VALID_INPUTS)}"
|
||||
return fmt.fmt_price_trend(client.input_cost_trend(item=it, years=years))
|
||||
return fmt.fmt_price_trend(client.input_cost_trend(item=it, years=years, geo=g))
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def input_cost_series(
|
||||
item: Annotated[str, Field(description="Input: 'diesel'.")],
|
||||
item: Annotated[
|
||||
str, Field(description="Input: 'diesel' or a fertilizer ('urea', 'uan', "
|
||||
"'anhydrous', 'dap', 'map', 'potash').")],
|
||||
geo: Annotated[
|
||||
str | None,
|
||||
Field(description="Fertilizer region (default 'Cornbelt'). Ignored for diesel."),
|
||||
] = None,
|
||||
) -> str:
|
||||
"""Raw historical series for a tracked input cost (diesel, $/gal)."""
|
||||
"""Raw historical series for a tracked input cost (diesel $/gal or fertilizer $/ton)."""
|
||||
it = item.strip().lower()
|
||||
with track("input_cost_series", item=it):
|
||||
g = geo.strip() if geo else None
|
||||
with track("input_cost_series", item=it, geo=g or ""):
|
||||
if it not in VALID_INPUTS:
|
||||
return f"`item` must be one of: {sorted(VALID_INPUTS)}"
|
||||
return fmt.fmt_price_series(client.input_cost_series(item=it))
|
||||
return fmt.fmt_price_series(client.input_cost_series(item=it, geo=g))
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@@ -131,6 +131,21 @@ def test_fmt_input_cost_payload_shape():
|
||||
assert "+58.2%" in out # YoY surfaced
|
||||
|
||||
|
||||
def test_fmt_input_cost_fertilizer_ton_and_region():
|
||||
# Fertilizer payload carries item + geo (region) + $/ton.
|
||||
payload = {"item": "urea", "geo": "Cornbelt", "label": "urea", "unit": "$/ton",
|
||||
"source": "USDA AgTransport",
|
||||
"trend": {"period": "2026-03-01", "value_cents": 69812, "prev_cents": 51812,
|
||||
"change_cents": 18000, "change_pct": 34.7, "yoy_cents": 45500,
|
||||
"yoy_change_cents": 24312, "yoy_pct": 53.4, "seasonal": None,
|
||||
"recent_direction": "up", "baseline_years": 10, "points": 39}}
|
||||
out = fmt.fmt_price_trend(payload)
|
||||
assert "urea — Cornbelt — urea" in out # item, region, label all present
|
||||
assert "$698.12/ton" in out
|
||||
assert "USDA AgTransport" in out
|
||||
assert "Mar 2026" in out # monthly period rendered as month
|
||||
|
||||
|
||||
def test_fmt_inputs_lime_table():
|
||||
payload = {
|
||||
"product": "lime", "count": 2,
|
||||
|
||||
Reference in New Issue
Block a user