"""JSON-from-ag-monitor → markdown helpers. One function per @mcp.tool. Markdown is what FastMCP tools return; Claude / OpenWebUI render it nicely. Cents → dollars formatting is centralized here so all tools use the same precision (4 decimals for $/bu, 2 decimals for $/ton). """ from __future__ import annotations from typing import Optional def _bu(cents: Optional[int]) -> str: if cents is None: return "—" return f"${cents / 100:.4f}" def _ton(cents: Optional[int]) -> str: if cents is None: return "—" return f"${cents / 100:.2f}" def _basis(cents: Optional[int]) -> str: if cents is None: return "—" sign = "+" if cents >= 0 else "" return f"{sign}{cents / 100:.2f}" def _delta_arrow(cents: Optional[int]) -> str: if cents is None or cents == 0: return "—" return "▲" if cents > 0 else "▼" # ---------- best_local_bid ---------- def fmt_best(commodity: str, payload: dict) -> str: best = payload.get("best") today = payload.get("today") if not best: return ( f"### Best place to sell {commodity} today ({today})\n\n" f"No current-month {commodity} bids posted across the tracked sources.\n" ) return ( f"### Best place to sell {commodity} today ({today})\n\n" f"**{best['source_name']}** — delivery **{best['delivery']}** — " f"bid **{_bu(best['bid_cents'])}/bu** (basis {_basis(best.get('basis_cents'))}, " f"futures {best.get('futures_contract') or '—'})\n\n" f"_Fetched {best.get('fetched_at') or '?'}_\n" ) # ---------- inputs / fertilizer ---------- def fmt_inputs(payload: dict) -> str: rows = payload.get("rows") or [] product = payload.get("product") title = f"### {product.upper()} prices" if product else "### Fertilizer + lime prices" if not rows: scope = product or "any tracked input" return f"{title}\n\nNo {scope} prices on file.\n" lines = [ title, "", "| Source | Product | Delivery | Price ($/ton) | Fetched |", "|---|---|---|---:|---|", ] for r in rows: lines.append( f"| {r['source_name']} | {r.get('display_name') or r['commodity']} | " f"{r['delivery']} | {_ton(r.get('bid_cents'))} | {r.get('fetched_at') or '?'} |" ) return "\n".join(lines) + "\n" # ---------- latest snapshot ---------- def fmt_latest(payload: dict) -> str: rows = payload.get("rows") or [] if not rows: return "### Latest prices\n\nNo rows match those filters.\n" lines = [ "### Latest prices", "", "| Source | Commodity | Delivery | Bid | Basis | Futures | Fetched |", "|---|---|---|---:|---:|---|---|", ] for r in rows: unit_fmt = _ton if r.get("commodity_kind") == "fertilizer" else _bu lines.append( f"| {r['source_name']} | {r.get('display_name') or r['commodity']} | " f"{r['delivery']} | {unit_fmt(r.get('bid_cents'))} | " f"{_basis(r.get('basis_cents'))} | {r.get('futures_contract') or '—'} | " f"{r.get('fetched_at') or '?'} |" ) return "\n".join(lines) + "\n" # ---------- price history ---------- def fmt_history(payload: dict, max_rows: int = 60) -> str: rows = payload.get("rows") or [] commodity = payload.get("commodity") days = payload.get("days") if not rows: return f"### {commodity} history ({days}d)\n\nNo samples in the window.\n" # Per (source, delivery) trend annotation: first vs last sample. series: dict[tuple, list[dict]] = {} for r in rows: series.setdefault((r["source_name"], r["delivery"]), []).append(r) lines = [f"### {commodity} price history — last {days} days", ""] for (src, dlv), pts in sorted(series.items()): if not pts: continue first = pts[0].get("bid_cents") last = pts[-1].get("bid_cents") delta = (last - first) if (first is not None and last is not None) else None arrow = _delta_arrow(delta) lines.append( f"- **{src}** / {dlv}: {len(pts)} samples · " f"{_bu(first)} → {_bu(last)} {arrow} {_basis(delta) if delta is not None else ''}".rstrip() ) # If the history is shallow include the raw rows too (helpful for charts). if sum(len(p) for p in series.values()) <= max_rows: lines.extend([ "", "| Time | Source | Delivery | Bid | Basis |", "|---|---|---|---:|---:|", ]) for r in rows[-max_rows:]: lines.append( f"| {r['fetched_at']} | {r['source_name']} | {r['delivery']} | " f"{_bu(r.get('bid_cents'))} | {_basis(r.get('basis_cents'))} |" ) return "\n".join(lines) + "\n" # ---------- sources / health ---------- def fmt_sources(payload: dict) -> str: src = payload.get("sources") or [] if not src: return "### Sources\n\nNo active sources.\n" lines = [ "### Tracked sources", "", "| Source | Kind | Last success | Consecutive failures | Last error |", "|---|---|---|---:|---|", ] for s in src: lines.append( f"| {s['name']} | {s['kind']} | {s.get('last_success_at') or '—'} | " f"{s.get('consecutive_failures') or 0} | {s.get('last_error') or ''} |" ) return "\n".join(lines) + "\n" def fmt_health(payload: dict) -> str: src = payload.get("sources") or [] healthy, stale, down = [], [], [] for s in src: n = s.get("consecutive_failures") or 0 if n >= 3: down.append(s) elif not s.get("last_success_at"): stale.append(s) else: healthy.append(s) lines = ["### Source health", ""] lines.append(f"- ✅ Healthy: **{len(healthy)}**") lines.append(f"- ⚠ Stale (never succeeded): **{len(stale)}**") lines.append(f"- ❌ Down (3+ consecutive failures): **{len(down)}**") if stale or down: lines.append("") lines.append("| Source | State | Last success | Failures | Error |") lines.append("|---|---|---|---:|---|") for s in stale + down: state = "stale" if s in stale else "down" lines.append( f"| {s['name']} | {state} | {s.get('last_success_at') or '—'} | " f"{s.get('consecutive_failures') or 0} | " f"{(s.get('last_error') or '')[:80]} |" ) return "\n".join(lines) + "\n" # ---------- list helpers ---------- def fmt_deliveries(payload: dict) -> str: commodity = payload.get("commodity") labels = payload.get("deliveries") or [] if not labels: return f"### {commodity} deliveries\n\nNo posted deliveries.\n" return ( f"### Posted delivery labels for {commodity}\n\n" + "\n".join(f"- {x}" for x in labels) + "\n" ) def fmt_commodities() -> str: return ( "### Tracked commodities\n\n" "- **corn** ($/bu)\n" "- **soy** — soybeans ($/bu)\n" "- **wheat** ($/bu) — tracked but excluded from daily brief emails\n" "- **map** — MAP 11-52-0 ($/ton)\n" "- **potash** — Potash 0-0-60 ($/ton)\n" "- **lime** ($/ton)\n" ) # ---------- today's summary ---------- def fmt_summary(payload: dict) -> str: today = payload.get("today") prev = payload.get("prev_trading_day") lines = [f"### Market summary — today {today} vs prev trading day {prev}", ""] for c in payload.get("commodities", []): f = c.get("futures") or {} chg = f.get("change_cents") arrow = _delta_arrow(chg) lines.append( f"**{c['display_name']}** — CBOT {f.get('contract','?')} " f"last {_bu(f.get('today_last_cents'))}, prev close " f"{_bu(f.get('prev_close_cents'))} {arrow} {_basis(chg) if chg is not None else ''}" ) bft = c.get("best_for_today") if bft: lines.append( f" · Best for today's delivery ({bft['delivery']}): " f"**{bft['source_name']}** @ {_bu(bft['bid_cents'])} " f"(basis {_basis(bft.get('basis_cents'))})" ) else: lines.append(" · No current-month local bid posted.") lines.append("") return "\n".join(lines).rstrip() + "\n"