Add basis_movement/basis_detail tools; make history + latest fully filterable
New MCP tools: - basis_movement: aggregated basis trend, one headline line per crop. The cheap "how is basis moving overall" view; optional commodity/source/delivery/days. - basis_detail: per-(elevator, crop, delivery) basis first→last drill-down. Both do the aggregation MCP-side and return compact markdown to keep token burn low, so a client can call the cheap aggregate first and drill in only when needed. Flexibility/parity changes: - price_history: commodity is now optional (spans all crops); groups by (source, commodity, delivery); surfaces basis first→last in the summary and adds a futures column to the raw table. - latest_prices: expose the `kind` filter (grain/fertilizer) that the API and client already supported. - client.history(): commodity optional. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,68 @@ def test_fmt_history_with_trend_arrow():
|
||||
assert "▲" in out
|
||||
assert "$4.8000" in out
|
||||
assert "$4.9600" in out
|
||||
# Basis trend is now surfaced in the summary line too.
|
||||
assert "basis +0.25 → +0.30" in out
|
||||
|
||||
|
||||
def _history_multi():
|
||||
return {
|
||||
"commodity": None, "days": 7,
|
||||
"rows": [
|
||||
{"source_name": "Minster", "commodity": "corn", "delivery": "Jul 2026",
|
||||
"bid_cents": 461, "basis_cents": 11, "futures_cents": 450,
|
||||
"fetched_at": "2026-05-23T15:00:00+00:00"},
|
||||
{"source_name": "Minster", "commodity": "corn", "delivery": "Jul 2026",
|
||||
"bid_cents": 464, "basis_cents": 14, "futures_cents": 450,
|
||||
"fetched_at": "2026-05-29T15:00:00+00:00"},
|
||||
{"source_name": "Minster", "commodity": "soy", "delivery": "Nov 2026",
|
||||
"bid_cents": 1145, "basis_cents": -50, "futures_cents": 1195,
|
||||
"fetched_at": "2026-05-23T15:00:00+00:00"},
|
||||
{"source_name": "Minster", "commodity": "soy", "delivery": "Nov 2026",
|
||||
"bid_cents": 1148, "basis_cents": -45, "futures_cents": 1193,
|
||||
"fetched_at": "2026-05-29T15:00:00+00:00"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_fmt_history_spans_crops_and_shows_basis_futures():
|
||||
out = fmt.fmt_history(_history_multi())
|
||||
# No commodity filter → "all crops" scope, both crops present.
|
||||
assert "all crops" in out
|
||||
assert "corn" in out and "soy" in out
|
||||
# Futures column populated from futures_cents.
|
||||
assert "$4.5000" in out
|
||||
# Basis movement annotated per series.
|
||||
assert "+0.11 → +0.14" in out
|
||||
assert "-0.50 → -0.45" in out
|
||||
|
||||
|
||||
def test_fmt_basis_movement_aggregates_per_crop():
|
||||
out = fmt.fmt_basis_movement(_history_multi())
|
||||
assert "Basis movement" in out
|
||||
# corn basis 0.11 → 0.14 (stronger), soy -0.50 → -0.45 (stronger)
|
||||
assert "**corn**" in out and "**soy**" in out
|
||||
assert "stronger" in out
|
||||
assert "1 elevators, 1 series" in out
|
||||
|
||||
|
||||
def test_fmt_basis_movement_skips_non_grain_and_nulls():
|
||||
payload = {"commodity": None, "days": 7, "rows": [
|
||||
{"source_name": "Coop", "commodity": "lime", "delivery": "spot",
|
||||
"bid_cents": 42000, "basis_cents": None, "fetched_at": "2026-05-23T15:00:00+00:00"},
|
||||
]}
|
||||
out = fmt.fmt_basis_movement(payload)
|
||||
assert "No grain basis samples" in out
|
||||
|
||||
|
||||
def test_fmt_basis_detail_per_series():
|
||||
out = fmt.fmt_basis_detail(_history_multi())
|
||||
assert "Basis movement by elevator" in out
|
||||
# One row per (crop, elevator, delivery)
|
||||
assert "| corn | Minster | Jul 2026 |" in out
|
||||
assert "| soy | Minster | Nov 2026 |" in out
|
||||
assert "weaker" not in out # both strengthened in this fixture
|
||||
assert "stronger" in out
|
||||
|
||||
|
||||
def test_fmt_sources_table():
|
||||
|
||||
Reference in New Issue
Block a user