Add input_cost_trend / input_cost_series MCP tools (EIA diesel)
CI / test (push) Successful in 17s
CI / build-push (push) Successful in 6s

Real input price + WoW/YoY change + seasonal for diesel ($/gal). Formatters now
handle the item/label payload shape. Changelog updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 12:35:32 -04:00
parent 47cac9b521
commit 457cdad2fb
5 changed files with 81 additions and 8 deletions
+8
View File
@@ -80,6 +80,14 @@ 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_series(item: str) -> dict:
return _get("/api/data/input-cost-series", item=item)
def futures(commodity: str, delivery: str | None = None) -> dict:
return _get("/api/data/futures", commodity=commodity, delivery=delivery)
+9 -6
View File
@@ -130,14 +130,16 @@ _MONTH_NAMES_3 = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
def fmt_price_trend(payload: dict, label: str = "price received") -> str:
commodity = payload.get("commodity")
name = payload.get("commodity") or payload.get("item")
geo = payload.get("geo")
label = payload.get("label") or label
unit = payload.get("unit") or "$/bu"
t = payload.get("trend")
loc = f"{geo}" if geo else ""
if not t:
return f"### {commodity}{geo}{label}\n\nNo data on file.\n"
return f"### {name}{loc}{label}\n\nNo data on file.\n"
when = _ym(t["period"])
lines = [f"### {commodity}{geo}{label}, {when}: {_unit_money(t['value_cents'], unit)}", ""]
lines = [f"### {name}{loc}{label}, {when}: {_unit_money(t['value_cents'], unit)}", ""]
arrow = _delta_arrow(t.get("change_cents"))
lines.append(f"- **Change:** {arrow} {_signed(t.get('change_cents'))} ({_pct(t.get('change_pct'))}) vs prior period")
if t.get("yoy_cents") is not None:
@@ -155,14 +157,15 @@ def fmt_price_trend(payload: dict, label: str = "price received") -> str:
def fmt_price_series(payload: dict, max_points: int = 60) -> str:
commodity = payload.get("commodity")
name = payload.get("commodity") or payload.get("item")
geo = payload.get("geo")
unit = payload.get("unit") or "$/bu"
series = payload.get("series") or []
loc = f"{geo}" if geo else ""
if not series:
return f"### {commodity}{geo} series\n\nNo data on file.\n"
return f"### {name}{loc} series\n\nNo data on file.\n"
shown = series[-max_points:]
head = [f"### {commodity}{geo}{payload.get('count', len(series))} periods "
head = [f"### {name}{loc}{payload.get('count', len(series))} periods "
f"(showing last {len(shown)})", "",
"| Period | Price |", "|---|---:|"]
body = [f"| {_ym(p['period'])} | {_unit_money(p['value_cents'], unit)} |" for p in shown]
+36
View File
@@ -303,6 +303,42 @@ def price_series(
return fmt.fmt_price_series(payload)
VALID_INPUTS = {"diesel"}
@mcp.tool()
def input_cost_trend(
item: Annotated[
str, Field(description="Input to price. Currently: 'diesel' (U.S. retail $/gal).")
],
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).
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."""
it = item.strip().lower()
with track("input_cost_trend", item=it, 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))
@mcp.tool()
def input_cost_series(
item: Annotated[str, Field(description="Input: 'diesel'.")],
) -> str:
"""Raw historical series for a tracked input cost (diesel, $/gal)."""
it = item.strip().lower()
with track("input_cost_series", item=it):
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))
@mcp.tool()
def list_sources() -> str:
"""All active scrapers + their last-success timestamps and any pending failures."""