fb50c103d3
Thread zip/lat/lng/radius_miles through the client and both tools; friendly guard for the zip-XOR-gps rule. Formatters surface distance, the searched center, and the nearest-source hint when nothing is in range. - client: best()/latest() take zip/lat/lng/radius_miles - server: location params + docstrings (note Ohio-concentrated coverage) - format: distance column + center/nearest rendering - README + CHANGELOG + advisor prompt library updated - tests: location formatting cases Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
393 lines
16 KiB
Python
393 lines
16 KiB
Python
"""Markdown formatter tests — no network, just JSON-in / markdown-out."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
import sys
|
||
|
||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||
|
||
from ag_bids_mcp import format as fmt
|
||
|
||
|
||
def test_fmt_best_with_winner():
|
||
payload = {
|
||
"commodity": "corn", "today": "2026-05-20",
|
||
"best": {
|
||
"source_name": "Mercer Landmark — St Henry",
|
||
"delivery": "May 2026",
|
||
"bid_cents": 491, "basis_cents": 33,
|
||
"futures_contract": "ZCK26",
|
||
"fetched_at": "2026-05-20T15:00:00+00:00",
|
||
},
|
||
}
|
||
out = fmt.fmt_best("corn", payload)
|
||
assert "Mercer Landmark — St Henry" in out
|
||
assert "$4.9100/bu" in out
|
||
assert "May 2026" in out
|
||
assert "+0.33" in out
|
||
|
||
|
||
def test_fmt_best_no_winner():
|
||
payload = {"commodity": "wheat", "today": "2026-05-20", "best": None}
|
||
out = fmt.fmt_best("wheat", payload)
|
||
assert "No current-month" in out
|
||
assert "wheat" in out
|
||
|
||
|
||
def test_fmt_best_with_location_shows_distance():
|
||
payload = {
|
||
"commodity": "corn", "today": "2026-05-20",
|
||
"center": {"lat": 40.78, "lng": -83.81, "zip": "45810", "source": "zip"},
|
||
"radius_miles": 50.0,
|
||
"best": {
|
||
"source_name": "Heritage Cooperative — Ada", "city": "Ada", "state": "OH",
|
||
"delivery": "May 2026", "bid_cents": 486, "basis_cents": 20,
|
||
"futures_contract": "ZCN26", "distance_miles": 2.3,
|
||
"fetched_at": "2026-05-20T15:00:00+00:00",
|
||
},
|
||
}
|
||
out = fmt.fmt_best("corn", payload)
|
||
assert "(Ada, OH)" in out
|
||
assert "2.3 mi" in out
|
||
assert "within 50 mi of ZIP 45810" in out
|
||
|
||
|
||
def test_fmt_best_out_of_range_reports_nearest():
|
||
payload = {
|
||
"commodity": "corn", "today": "2026-05-20", "best": None,
|
||
"center": {"lat": 42.0, "lng": -93.6, "zip": "50010", "source": "zip"},
|
||
"radius_miles": 50.0,
|
||
"nearest": {"source_name": "Heritage Cooperative — Ada", "city": "Ada",
|
||
"state": "OH", "distance_miles": 513.5},
|
||
}
|
||
out = fmt.fmt_best("corn", payload)
|
||
assert "No current-month corn bids within 50 mi of ZIP 50010" in out
|
||
assert "Nearest tracked elevator" in out
|
||
assert "513.5 mi" in out
|
||
|
||
|
||
def test_fmt_latest_with_location_adds_distance_column():
|
||
payload = {
|
||
"center": {"lat": 40.78, "lng": -83.81, "zip": "45810", "source": "zip"},
|
||
"radius_miles": 50.0,
|
||
"rows": [
|
||
{"source_name": "Heritage Cooperative — Ada", "commodity": "corn",
|
||
"display_name": "Corn", "commodity_kind": "grain", "delivery": "May 2026",
|
||
"bid_cents": 486, "basis_cents": 20, "futures_contract": "ZCN26",
|
||
"distance_miles": 2.3, "fetched_at": "2026-05-20T15:00:00+00:00"},
|
||
],
|
||
}
|
||
out = fmt.fmt_latest(payload)
|
||
assert "Distance" in out and "2.3 mi" in out
|
||
assert "within 50 mi of ZIP 45810" in out
|
||
|
||
|
||
def test_fmt_latest_location_no_rows():
|
||
payload = {"center": {"zip": "50010", "source": "zip"}, "radius_miles": 50.0, "rows": []}
|
||
out = fmt.fmt_latest(payload)
|
||
assert "No sources within 50 mi of ZIP 50010" in out
|
||
|
||
|
||
def test_fmt_futures_with_changes():
|
||
payload = {
|
||
"commodity": "corn", "delivery": "Jul 2026", "contract": "ZCN26",
|
||
"symbol": "ZC",
|
||
"quote": {
|
||
"settle_date": "2026-05-29", "open_cents": 461, "last_cents": 455,
|
||
"prev_close_cents": 460, "change_since_open_cents": -6,
|
||
"change_on_day_cents": -5, "fetched_at": "2026-05-29T18:00:00+00:00",
|
||
},
|
||
}
|
||
out = fmt.fmt_futures(payload)
|
||
assert "ZCN26" in out and "Jul 2026" in out
|
||
assert "$4.5500" in out # last
|
||
assert "$4.6100" in out # open
|
||
assert "▼ -0.06" in out # change since open
|
||
assert "▼ -0.05" in out # change on day
|
||
|
||
|
||
def test_fmt_futures_no_open_yet():
|
||
payload = {
|
||
"commodity": "soy", "delivery": None, "contract": "continuous",
|
||
"symbol": "ZS=F",
|
||
"quote": {
|
||
"settle_date": "2026-05-29", "open_cents": None, "last_cents": 1180,
|
||
"prev_close_cents": 1177, "change_since_open_cents": None,
|
||
"change_on_day_cents": 3, "fetched_at": "2026-05-29T18:00:00+00:00",
|
||
},
|
||
}
|
||
out = fmt.fmt_futures(payload)
|
||
assert "continuous nearby" in out
|
||
assert "no open captured yet" in out
|
||
assert "▲ +0.03" in out # change on day still shown
|
||
|
||
|
||
def test_fmt_futures_no_quote():
|
||
out = fmt.fmt_futures({"commodity": "wheat", "delivery": None,
|
||
"contract": "continuous", "quote": None})
|
||
assert "No futures quote on file" in out
|
||
|
||
|
||
def test_fmt_price_trend():
|
||
payload = {"commodity": "corn", "geo": "OH", "unit": "$/bu",
|
||
"source": "USDA NASS Quick Stats",
|
||
"trend": {"period": "2026-04-01", "value_cents": 468,
|
||
"prev_cents": 459, "change_cents": 9, "change_pct": 2.0,
|
||
"yoy_cents": 480, "yoy_change_cents": -12, "yoy_pct": -2.5,
|
||
"seasonal": {"normal_cents": 435, "median_cents": 430,
|
||
"min_cents": 400, "max_cents": 480,
|
||
"percentile": 75, "vs_normal_pct": 7.6,
|
||
"sample_years": 4},
|
||
"recent_direction": "up", "baseline_years": 10, "points": 120}}
|
||
out = fmt.fmt_price_trend(payload)
|
||
assert "corn — OH" in out
|
||
assert "$4.68/bu" in out
|
||
assert "+$0.09" in out and "+2.0%" in out # MoM change
|
||
assert "−$0.12" in out and "−2.5%" in out # YoY
|
||
assert "75th pct" in out
|
||
|
||
|
||
def test_fmt_price_trend_empty():
|
||
out = fmt.fmt_price_trend({"commodity": "wheat", "geo": "US", "unit": "$/bu", "trend": None})
|
||
assert "No data on file" in out
|
||
|
||
|
||
def test_fmt_price_series():
|
||
payload = {"commodity": "corn", "geo": "US", "unit": "$/bu", "count": 2,
|
||
"series": [{"period": "2026-03-01", "value_cents": 459},
|
||
{"period": "2026-04-01", "value_cents": 468}]}
|
||
out = fmt.fmt_price_series(payload)
|
||
assert "Mar 2026" in out and "$4.59/bu" in out
|
||
assert "Apr 2026" in out and "$4.68/bu" in out
|
||
|
||
|
||
def test_fmt_price_trend_diesel_units():
|
||
payload = {"commodity": "diesel", "geo": "US", "unit": "$/gal", "source": "EIA",
|
||
"trend": {"period": "2026-05-25", "value_cents": 552, "prev_cents": 560,
|
||
"change_cents": -8, "change_pct": -1.4, "yoy_cents": None,
|
||
"yoy_change_cents": None, "yoy_pct": None, "seasonal": None,
|
||
"recent_direction": "down", "baseline_years": 10, "points": 200}}
|
||
out = fmt.fmt_price_trend(payload, label="retail diesel")
|
||
assert "$5.520/gal" in out # 3-decimal $/gal formatting
|
||
|
||
|
||
def test_fmt_input_cost_payload_shape():
|
||
# Input-cost payload uses item/label and has no geo.
|
||
payload = {"item": "diesel", "label": "retail diesel", "unit": "$/gal", "source": "EIA",
|
||
"trend": {"period": "2026-05-25", "value_cents": 552, "prev_cents": 560,
|
||
"change_cents": -8, "change_pct": -1.4, "yoy_cents": 349,
|
||
"yoy_change_cents": 203, "yoy_pct": 58.2, "seasonal": None,
|
||
"recent_direction": "down", "baseline_years": 10, "points": 1680}}
|
||
out = fmt.fmt_price_trend(payload)
|
||
assert "diesel — retail diesel" in out # item/label header, no geo segment
|
||
assert "$5.520/gal" in out
|
||
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,
|
||
"rows": [
|
||
{"source_name": "Bob's Lime Quote", "commodity": "lime",
|
||
"display_name": "Lime", "delivery": "spot",
|
||
"bid_cents": 42000, "commodity_kind": "fertilizer",
|
||
"fetched_at": "2026-05-20T15:00:00+00:00"},
|
||
{"source_name": "Coop X", "commodity": "lime",
|
||
"display_name": "Lime", "delivery": "spot",
|
||
"bid_cents": 45500, "commodity_kind": "fertilizer",
|
||
"fetched_at": "2026-05-20T15:01:00+00:00"},
|
||
],
|
||
}
|
||
out = fmt.fmt_inputs(payload)
|
||
assert "LIME prices" in out
|
||
assert "$420.00" in out
|
||
assert "$455.00" in out
|
||
assert "Bob's Lime Quote" in out
|
||
|
||
|
||
def test_fmt_inputs_empty():
|
||
out = fmt.fmt_inputs({"product": "lime", "rows": []})
|
||
assert "No lime prices on file." in out
|
||
|
||
|
||
def test_fmt_latest_table_picks_correct_unit():
|
||
payload = {"count": 2, "rows": [
|
||
{"source_name": "Bambauer", "commodity": "corn",
|
||
"display_name": "Corn", "delivery": "May 2026",
|
||
"bid_cents": 458, "basis_cents": 2, "commodity_kind": "grain",
|
||
"futures_contract": "ZCN26", "fetched_at": "2026-05-20T15:00:00+00:00"},
|
||
{"source_name": "Coop X", "commodity": "lime",
|
||
"display_name": "Lime", "delivery": "spot",
|
||
"bid_cents": 42000, "basis_cents": None, "commodity_kind": "fertilizer",
|
||
"futures_contract": None, "fetched_at": "2026-05-20T15:00:00+00:00"},
|
||
]}
|
||
out = fmt.fmt_latest(payload)
|
||
assert "$4.5800" in out # grain in /bu (4 decimals)
|
||
assert "$420.00" in out # fertilizer in /ton (2 decimals)
|
||
|
||
|
||
def test_fmt_history_with_trend_arrow():
|
||
payload = {
|
||
"commodity": "corn", "days": 7,
|
||
"rows": [
|
||
{"source_name": "Andersons", "delivery": "May 2026",
|
||
"bid_cents": 480, "basis_cents": 25,
|
||
"fetched_at": "2026-05-15T15:00:00+00:00"},
|
||
{"source_name": "Andersons", "delivery": "May 2026",
|
||
"bid_cents": 496, "basis_cents": 30,
|
||
"fetched_at": "2026-05-20T15:00:00+00:00"},
|
||
],
|
||
}
|
||
out = fmt.fmt_history(payload)
|
||
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():
|
||
payload = {"sources": [
|
||
{"id": 1, "name": "Test Elev", "kind": "elevator",
|
||
"city": "Ada", "state": "OH", "zip": "45810",
|
||
"last_success_at": "2026-05-20T14:55:00+00:00",
|
||
"consecutive_failures": 0, "last_error": None},
|
||
]}
|
||
out = fmt.fmt_sources(payload)
|
||
assert "Test Elev" in out
|
||
assert "elevator" in out
|
||
assert "Ada, OH 45810" in out # location column
|
||
|
||
|
||
def test_fmt_health_buckets():
|
||
payload = {"sources": [
|
||
{"name": "Healthy", "kind": "elevator",
|
||
"last_success_at": "2026-05-20T14:55:00+00:00",
|
||
"consecutive_failures": 0, "last_error": None},
|
||
{"name": "Stale", "kind": "elevator",
|
||
"last_success_at": None,
|
||
"consecutive_failures": 0, "last_error": None},
|
||
{"name": "Down", "kind": "elevator",
|
||
"last_success_at": "2026-05-15T14:55:00+00:00",
|
||
"consecutive_failures": 5, "last_error": "boom"},
|
||
]}
|
||
out = fmt.fmt_health(payload)
|
||
assert "Healthy: **1**" in out
|
||
assert "Stale (never succeeded): **1**" in out
|
||
assert "Down" in out and "**1**" in out
|
||
# Down section lists boom
|
||
assert "boom" in out
|
||
|
||
|
||
def test_fmt_deliveries():
|
||
out = fmt.fmt_deliveries({"commodity": "corn",
|
||
"deliveries": ["May 2026", "Jul 2026"]})
|
||
assert "- May 2026" in out
|
||
assert "- Jul 2026" in out
|
||
|
||
|
||
def test_fmt_commodities_lists_all_six():
|
||
out = fmt.fmt_commodities()
|
||
for name in ("corn", "soy", "wheat", "map", "potash", "lime"):
|
||
assert name in out.lower()
|
||
|
||
|
||
def test_fmt_summary_includes_best_for_today():
|
||
payload = {
|
||
"today": "2026-05-20", "prev_trading_day": "2026-05-19",
|
||
"commodities": [
|
||
{"symbol": "corn", "display_name": "Corn",
|
||
"futures": {"contract": "ZC=F", "today_last_cents": 458,
|
||
"prev_close_cents": 455, "change_cents": 3},
|
||
"best_for_today": {
|
||
"source_name": "Mercer Landmark — St Henry",
|
||
"delivery": "May 2026", "bid_cents": 491, "basis_cents": 33,
|
||
}},
|
||
{"symbol": "soy", "display_name": "Soybeans",
|
||
"futures": {"contract": "ZS=F", "today_last_cents": 1180,
|
||
"prev_close_cents": 1177, "change_cents": 3},
|
||
"best_for_today": None},
|
||
],
|
||
}
|
||
out = fmt.fmt_summary(payload)
|
||
assert "Corn" in out and "Soybeans" in out
|
||
assert "Mercer Landmark — St Henry" in out
|
||
assert "$4.5800" in out # corn last
|
||
assert "No current-month local bid posted" in out # soy fallback
|