""" Intelligence layer: Claude + JEV + NVIDIA NIM working as a team. Architecture: Claude (Anthropic) — strategist: reads the lake snapshot, identifies overhyped lines, hidden correlations, best spots. Returns natural language analysis + specific testable hypotheses. JEV (TypeSafe) — probability engine: given each Claude hypothesis, scores it as a structured Noul probability (0-1). NVIDIA NIM — validator: independent second LLM pass on the top consensus findings for cross-verification. All three degrade gracefully when their key is absent. The result always reports which providers contributed so the UI can show connection state. Daily cached (same as jimmy_hunt). Force refresh with invalidate_cache(). """ from __future__ import annotations import json import os import textwrap from datetime import datetime, timezone from functools import lru_cache from typing import Any import data import jimmy import jimmy_hunt import jev # --------------------------------------------------------------------------- # Provider availability # --------------------------------------------------------------------------- @lru_cache(maxsize=1) def _anthropic_client(): try: import anthropic key = os.environ.get("ANTHROPIC_API_KEY") if not key: return None return anthropic.Anthropic(api_key=key) except ImportError: return None def claude_available() -> bool: return _anthropic_client() is not None def nvidia_available() -> bool: return bool(os.environ.get("NVIDIA_API_KEY")) _GEMMA_LOCAL_URL = "http://127.0.0.1:8765" def gemma_available() -> bool: """Gemma runs locally via Ollama on port 8765.""" try: import httpx r = httpx.get(f"{_GEMMA_LOCAL_URL}/health", timeout=3.0) return r.status_code == 200 except Exception: return False def ai_status() -> dict: jev_diag = jev.diagnose() return { "claude": { "connected": claude_available(), "model": "claude-opus-5-5", "keyVar": "ANTHROPIC_API_KEY", }, "jev": { "connected": jev.available(), "model": "jev-latest", "keyVar": jev_diag.get("activeKeyVar"), "callTest": jev_diag.get("callTest"), }, "nvidia": { "connected": nvidia_available(), "model": "meta/llama-3.2-11b-vision-instruct", "keyVar": "NVIDIA_API_KEY", }, "gemma": { "connected": gemma_available(), "model": "gemma3:4b Q4 (local)", "keyVar": "Ollama :8765", }, } # --------------------------------------------------------------------------- # Lake snapshot — compact context for the LLMs # --------------------------------------------------------------------------- def _lake_snapshot() -> str: """ A compact but complete picture of this week's lake: matchup edges, top-usage players, hunt picks, and recent hit-rate vs lines. Kept under ~6K tokens so Claude can reason over it efficiently. """ lines: list[str] = [] # Matchup edges for this week try: from matchup import slate games = slate() lines.append("=== WEEK 3 MATCHUP EDGES (z-score, + favors offense) ===") for g in games[:8]: h, a = g["home"], g["away"] edge_str = " | ".join(f"{u}:{h['edges'].get(u,0):+.2f}" for u in ["SCORING","AIR","GROUND"]) lines.append(f"{h['team']} vs {a['team']} — HOME: {edge_str}") except Exception as e: lines.append(f"[matchup snapshot error: {e}]") # Top-usage players by position try: usage_rows = sorted( [r for r in data.load("player_usage") if r.get("usage_index_score")], key=lambda r: float(r.get("usage_index_score", 0)), reverse=True ) lines.append("\n=== TOP USAGE PLAYERS (coach trust score) ===") pos_seen: dict[str, int] = {} for r in usage_rows: pos = (r.get("position") or "").upper() if pos not in ("QB","RB","WR","TE"): continue if pos_seen.get(pos, 0) >= 3: continue pos_seen[pos] = pos_seen.get(pos, 0) + 1 lines.append(f"{pos} {r['player_name']} ({r['team']}) usage={r['usage_index_score']}") except Exception as e: lines.append(f"[usage snapshot error: {e}]") # Hunt picks — Jimmy's best per question try: hunt = jimmy_hunt.hunt() lines.append("\n=== JIMMY'S TOP PICKS THIS WEEK ===") for q in hunt["questions"][:12]: if q["players"]: best = q["players"][0] lines.append( f"{q['question']}: {best['name']} ({best['team']}) " f"jimmy={best['jimmyScore']} jev={best.get('jevScore','?')} " f"defWeakness={best.get('defWeakness','?')}" ) except Exception as e: lines.append(f"[hunt snapshot error: {e}]") # Recent performance vs lines (L2, since we only have 2 weeks) try: lines.append("\n=== L2 PERFORMANCE VS FANDUEL LINE (sample players) ===") from jimmy_hunt import _hit_rate sample = [("00-0036963","A.St. Brown","WR","recyds",80.5), ("00-0033280","C.McCaffrey","RB","rushyds",75.5), ("00-0035228","J.Jefferson","WR","recyds",80.5)] for pid, name, pos, mkt, line_ in sample: hr = _hit_rate(pid, mkt, line_, "over") lines.append(f"{pos} {name}: L2 hit rate {mkt}>{line_} = {hr}") except Exception as e: lines.append(f"[L2 snapshot error: {e}]") return "\n".join(lines) # --------------------------------------------------------------------------- # Claude analysis # --------------------------------------------------------------------------- _CLAUDE_SYSTEM = textwrap.dedent(""" You are Jimmy the Greek's analytical brain — a sharp, concise NFL prop analyst with deep knowledge of how betting markets misprice player props in early-season games when sample sizes are small and books rely on reputation rather than data. You receive a lake snapshot: matchup unit edges, usage/coach-trust scores, and prop-pick recommendations. Your job: 1. IDENTIFY OVERHYPED PROPS — lines the market has set inflated vs actual lake signals (e.g., a WR the offense clearly trusts less than their line implies). 2. FIND CORRELATIONS — pairs of props that tend to move together (e.g., if a team is weak AIR unit, the QB's yards AND the WR's yards both get a boost). 3. SPOT VALUE — props where the lake says yes but the market is undervaluing (defWeakness high, usage score high, matchup edge positive). Output exactly 5–8 "nuggets" as a JSON array (nothing else — no prose wrapper): [ { "rank": 1, "claim": "one specific, actionable claim about a player/prop", "category": "overhyped" | "correlation" | "value", "action": "BET OVER / BET UNDER / PARLAY WITH [X]", "reasoning": "2-3 sentences max", "testable": "a yes/no question JEV can score" } ] """).strip() def _ask_claude(lake_ctx: str, query: str | None = None) -> list[dict]: client = _anthropic_client() if not client: return [] try: import anthropic prompt = lake_ctx if query: prompt += f"\n\n=== USER QUERY ===\n{query}\n\nAnswer the query using the lake data above. Still output 5-8 nuggets as JSON." msg = client.messages.create( model="claude-opus-5-5", max_tokens=1500, system=_CLAUDE_SYSTEM, messages=[{"role": "user", "content": prompt}], ) raw = msg.content[0].text.strip() # Strip markdown code fences if present if raw.startswith("```"): raw = raw.split("```")[1] if raw.startswith("json"): raw = raw[4:] return json.loads(raw) except Exception as e: return [{"error": str(e), "rank": 0}] # --------------------------------------------------------------------------- # NVIDIA NIM — primary generation (when Claude absent) + validation # --------------------------------------------------------------------------- _NVIDIA_BASE = "https://integrate.api.nvidia.com/v1" _NVIDIA_MODEL = "meta/llama-3.2-11b-vision-instruct" def _nvidia_generate(lake_ctx: str) -> list[dict]: """Use NVIDIA as primary nugget generator when Claude is unavailable.""" key = os.environ.get("NVIDIA_API_KEY") if not key: return [] try: import httpx resp = httpx.post( f"{_NVIDIA_BASE}/chat/completions", headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, json={ "model": _NVIDIA_MODEL, "messages": [ {"role": "system", "content": _CLAUDE_SYSTEM}, {"role": "user", "content": lake_ctx}, ], "max_tokens": 1500, "temperature": 0.4, }, timeout=90.0, ) resp.raise_for_status() raw = resp.json()["choices"][0]["message"]["content"].strip() if raw.startswith("```"): raw = raw.split("```")[1] if raw.startswith("json"): raw = raw[4:] return json.loads(raw) except Exception as e: return [{"error": str(e), "rank": 0}] def _ask_nvidia(nuggets: list[dict], lake_ctx: str) -> dict[int, str]: """Returns {rank: validation_comment} for the top nuggets.""" key = os.environ.get("NVIDIA_API_KEY") if not key or not nuggets: return {} try: import httpx top = [n for n in nuggets if "rank" in n and "testable" in n][:4] if not top: return {} questions = "\n".join(f"{n['rank']}. {n['testable']}" for n in top) prompt = ( f"You are an independent NFL analytics validator. Given this week's data " f"context and a set of betting prop claims, assess each claim's validity.\n\n" f"DATA CONTEXT:\n{lake_ctx[:2000]}\n\n" f"CLAIMS TO VALIDATE (answer each in 1 sentence — agree/disagree + why):\n{questions}" ) resp = httpx.post( f"{_NVIDIA_BASE}/chat/completions", headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, json={ "model": _NVIDIA_MODEL, "messages": [{"role": "user", "content": prompt}], "max_tokens": 600, "temperature": 0.3, }, timeout=60.0, ) resp.raise_for_status() text = resp.json()["choices"][0]["message"]["content"] # Parse line-by-line, map back to rank out = {} for line in text.strip().split("\n"): for n in top: if str(n["rank"]) in line[:5]: out[n["rank"]] = line.strip().lstrip("0123456789. ") break return out except Exception as e: return {0: f"NVIDIA error: {str(e)[:120]}"} # --------------------------------------------------------------------------- # Gemma 3 ZeroGPU analysis (HF Space) # --------------------------------------------------------------------------- def _ask_gemma(lake_ctx: str) -> list[dict]: """Call local Gemma 3 server (Ollama Q4, port 8765).""" try: import httpx resp = httpx.post( f"{_GEMMA_LOCAL_URL}/predict", json={"data": [lake_ctx[:4000]]}, headers={"Content-Type": "application/json"}, timeout=120.0, # CPU inference on 4B Q4 can take 60-90s ) resp.raise_for_status() raw = resp.json().get("data", ["[]"])[0] if isinstance(raw, str): if raw.startswith("```"): raw = raw.split("```")[1] if raw.startswith("json"): raw = raw[4:] return json.loads(raw) return raw if isinstance(raw, list) else [] except Exception as e: return [{"error": f"Gemma: {str(e)[:120]}", "rank": 0}] # --------------------------------------------------------------------------- # JEV scoring of Claude's hypotheses # --------------------------------------------------------------------------- def _score_nuggets_with_jev(nuggets: list[dict]) -> list[dict]: """Ask JEV to score each nugget's 'testable' question as a Noul probability.""" if not jev.available() or not nuggets: return nuggets enriched = [] for n in nuggets: if "error" in n or not n.get("testable"): enriched.append(n) continue try: prob = jev.leg_probability( player_id=n.get("claim", "unknown")[:20], name=n.get("claim", "")[:40], team="", market_slug="nugget", direction="over", line=0.5, hit_rate=None, usage_score=None, matchup_edge=None, opponent=None, ) enriched.append({**n, "jevProbability": prob}) except Exception: enriched.append(n) return enriched # --------------------------------------------------------------------------- # Daily cache # --------------------------------------------------------------------------- _cache: dict | None = None _cache_date: str = "" def invalidate_cache() -> None: global _cache, _cache_date _cache = None _cache_date = "" def nuggets(force: bool = False) -> dict: global _cache, _cache_date today = datetime.now(timezone.utc).strftime("%Y-%m-%d") if not force and _cache and _cache_date == today: return _cache result = _build_nuggets(today) _cache = result _cache_date = today return result def _build_nuggets(today: str) -> dict: lake_ctx = _lake_snapshot() # Primary nugget generation — Claude preferred, NVIDIA fallback claude_nuggets = _ask_claude(lake_ctx) claude_ok = bool(claude_nuggets and not any("error" in n for n in claude_nuggets)) if not claude_ok: # Claude unavailable — NVIDIA generates the nuggets nvidia_primary = _nvidia_generate(lake_ctx) primary_nuggets = nvidia_primary if nvidia_primary and not any("error" in n for n in nvidia_primary) else [] primary_source = "nvidia" else: primary_nuggets = claude_nuggets primary_source = "claude" # Gemma: independent open-weight perspective gemma_nuggets = _ask_gemma(lake_ctx) gemma_ok = bool(gemma_nuggets and not any("error" in n for n in gemma_nuggets)) # Merge primary + Gemma before JEV scoring — tag source combined: list[dict] = [] for n in primary_nuggets: combined.append({**n, "source": primary_source}) for n in (gemma_nuggets if gemma_ok else []): combined.append({**n, "rank": n.get("rank", 0) + 100, "source": "gemma"}) # JEV: probability scores on each claim if combined and not any("error" in n for n in combined): scored = _score_nuggets_with_jev(combined) else: scored = combined # NVIDIA: independent validation nvidia_comments = _ask_nvidia(scored, lake_ctx) # Merge and rank by consensus final = [] for n in scored: rank = n.get("rank", 99) jev_p = n.get("jevProbability") nvidia_note = nvidia_comments.get(rank) # Consensus score: average of what we have signals = [] if jev_p is not None: signals.append(jev_p) if nvidia_note and any(word in nvidia_note.lower() for word in ("agree", "correct", "strong", "confirm", "yes")): signals.append(0.75) elif nvidia_note and any(word in nvidia_note.lower() for word in ("disagree", "weak", "unlikely", "no")): signals.append(0.35) consensus = round(sum(signals) / len(signals), 3) if signals else None final.append({ **n, "jevProbability": jev_p, "nvidiaNote": nvidia_note, "consensusScore": consensus, }) # Sort by consensus desc, then by rank final.sort(key=lambda x: (-(x.get("consensusScore") or 0), x.get("rank", 99))) return { "date": today, "generatedAt": datetime.now(timezone.utc).isoformat(), "providers": { "claude": claude_available(), "jev": jev.available(), "nvidia": nvidia_available(), "gemma": gemma_ok, }, "nuggets": final, "lakeSnapshot": lake_ctx[:500] + "…", } # --------------------------------------------------------------------------- # Deep dive: ad-hoc query through all three AIs # --------------------------------------------------------------------------- def deep_dive(query: str) -> dict: lake_ctx = _lake_snapshot() # Claude answers the question claude_response = None if claude_available(): raw = _ask_claude(lake_ctx, query=query) claude_response = raw # NVIDIA independent take nvidia_response = None key = os.environ.get("NVIDIA_API_KEY") if key: try: import httpx resp = httpx.post( f"{_NVIDIA_BASE}/chat/completions", headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}, json={ "model": _NVIDIA_MODEL, "messages": [ {"role": "system", "content": "You are a sharp NFL prop analyst. Be concise and specific. Use data provided."}, {"role": "user", "content": f"DATA:\n{lake_ctx[:3000]}\n\nQUESTION: {query}\n\nAnswer in 3-5 sentences focused on actionable prop insights."}, ], "max_tokens": 400, "temperature": 0.4, }, timeout=20.0, ) resp.raise_for_status() nvidia_response = resp.json()["choices"][0]["message"]["content"] except Exception as e: nvidia_response = f"[NVIDIA error: {str(e)[:100]}]" # Gemma quick take gemma_response = None try: lake_short = lake_ctx[:2000] + f"\n\nQUESTION: {query}\nAnswer with 5 focused nuggets on this question." raw_g = _ask_gemma(lake_short) if raw_g and not any("error" in n for n in raw_g): gemma_response = raw_g except Exception: pass return { "query": query, "timestamp": datetime.now(timezone.utc).isoformat(), "providers": { "claude": claude_available(), "jev": jev.available(), "nvidia": nvidia_available(), "gemma": gemma_response is not None, }, "claudeNuggets": claude_response, "nvidiaResponse": nvidia_response, "gemmaResponse": gemma_response, }