nba-quant-2 / deploy.py
LBJLincoln's picture
refactor: remove LiteLLM dependency β€” direct provider calls only
21a5c88
#!/usr/bin/env python3
"""Deploy NBA Quant AI to lbjlincoln/nomos-nba-quant HF Space.
Uploads all files from hf-space/ dir, configures secrets, restarts.
Usage:
source .env.local
python3 hf-space/deploy.py
"""
import os, sys
from pathlib import Path
from huggingface_hub import HfApi, CommitOperationAdd
SPACE_ID = "lbjlincoln/nomos-nba-quant"
HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HF_TOKEN_2")
LOCAL_DIR = Path(__file__).parent
SECRETS = {
# ── Core DB ──
"DATABASE_URL": os.environ.get("DATABASE_URL", ""),
"SUPABASE_URL": os.environ.get("SUPABASE_URL", ""),
"SUPABASE_API_KEY": os.environ.get("SUPABASE_API_KEY", ""),
"SUPABASE_PASSWORD": os.environ.get("SUPABASE_PASSWORD", ""),
"SUPABASE_URL_2": os.environ.get("SUPABASE_URL_2", ""),
"SUPABASE_ANON_KEY_2": os.environ.get("SUPABASE_ANON_KEY_2", ""),
"SUPABASE_PASSWORD_2": os.environ.get("SUPABASE_PASSWORD_2", ""),
"SUPABASE_POOLER_2": os.environ.get("SUPABASE_POOLER_2", ""),
# ── Neo4j ──
"NEO4J_URI": os.environ.get("NEO4J_URI", ""),
"NEO4J_USER": os.environ.get("NEO4J_USER", ""),
"NEO4J_PASSWORD": os.environ.get("NEO4J_PASSWORD", ""),
"NEO4J_URI_2": os.environ.get("NEO4J_URI_2", ""),
"NEO4J_USER_2": os.environ.get("NEO4J_USER_2", ""),
"NEO4J_PASSWORD_2": os.environ.get("NEO4J_PASSWORD_2", ""),
# ── Pinecone ──
"PINECONE_API_KEY": os.environ.get("PINECONE_API_KEY", ""),
"PINECONE_API_KEY_2": os.environ.get("PINECONE_API_KEY_2", ""),
"PINECONE_HOST": os.environ.get("PINECONE_HOST", ""),
# ── Sports / Odds ──
"ODDS_API_KEY": os.environ.get("ODDS_API_KEY", ""),
# ── LLM Keys (for CrewAI, agents, research) ──
"OPENROUTER_API_KEY": os.environ.get("OPENROUTER_API_KEY", ""),
"OPENROUTER_KEY_QUANTITATIVE": os.environ.get("OPENROUTER_KEY_QUANTITATIVE", ""),
"OPENROUTER_KEY_SPARE": os.environ.get("OPENROUTER_KEY_SPARE", ""),
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", ""),
"GROQ_API_KEY": os.environ.get("GROQ_API_KEY", ""),
"GROQ_API_KEY_2": os.environ.get("GROQ_API_KEY_2", ""),
"GROQ_API_KEY_3": os.environ.get("GROQ_API_KEY_3", ""),
"XAI_API_KEY": os.environ.get("XAI_API_KEY", ""),
"COHERE_API_KEY": os.environ.get("COHERE_API_KEY", ""),
"KIMI_API_KEY": os.environ.get("KIMI_API_KEY", ""),
# LiteLLM removed β€” direct provider calls only
# ── Telegram ──
"TELEGRAM_BOT_TOKEN": os.environ.get("TELEGRAM_BOT_TOKEN", ""),
"ADMIN_TELEGRAM_ID": os.environ.get("ADMIN_TELEGRAM_ID", ""),
"TELEGRAM_CHANNEL_ID": os.environ.get("TELEGRAM_CHANNEL_ID", ""),
# ── Search / Research ──
"BRAVE_API_KEY": os.environ.get("BRAVE_API_KEY", ""),
"TAVILY_API_KEY": os.environ.get("TAVILY_API_KEY", ""),
"EXA_API_KEY": os.environ.get("EXA_API_KEY", ""),
"JINA_API_KEY": os.environ.get("JINA_API_KEY", ""),
# ── GitHub ──
"GH_TOKEN": os.environ.get("GH_TOKEN", ""),
"GITHUB_TOKEN": os.environ.get("GITHUB_TOKEN", ""),
# ── HuggingFace ──
"HF_TOKEN": os.environ.get("HF_TOKEN", ""),
"HF_TOKEN_2": os.environ.get("HF_TOKEN_2", ""),
"HF_TOKEN_3": os.environ.get("HF_TOKEN_3", ""),
# ── Infrastructure ──
"VM_CALLBACK_URL": os.environ.get("VM_CALLBACK_URL", "http://34.136.180.66:8080"),
"VM_HOST": os.environ.get("VM_HOST", ""),
"REDIS_URL": os.environ.get("REDIS_URL", ""),
"REMOTE_CONTROL_KEY": os.environ.get("REMOTE_CONTROL_KEY", ""),
# ── Google ──
"GOOGLE_API_KEY": os.environ.get("GOOGLE_API_KEY", ""),
}
def main():
if not HF_TOKEN:
print("ERROR: HF_TOKEN not set. Run: source .env.local")
sys.exit(1)
# ── Engine parity check ──
root_engine = (LOCAL_DIR.parent / "features" / "engine.py").read_bytes()
hf_engine = (LOCAL_DIR / "features" / "engine.py").read_bytes()
if root_engine != hf_engine:
print("ERROR: features/engine.py and hf-space/features/engine.py have diverged!")
print("Fix: cp hf-space/features/engine.py features/engine.py")
print("Or: cp features/engine.py hf-space/features/engine.py")
sys.exit(1)
print("Engine parity check: OK")
api = HfApi(token=HF_TOKEN)
print(f"Deploying NBA Quant AI to {SPACE_ID}...")
operations = []
skip = {"__pycache__", ".pyc", "node_modules", ".git", "deploy.py"}
for fp in LOCAL_DIR.rglob("*"):
if fp.is_dir():
continue
if any(s in str(fp) for s in skip):
continue
rel = fp.relative_to(LOCAL_DIR)
print(f" + {rel}")
operations.append(CommitOperationAdd(path_in_repo=str(rel), path_or_fileobj=str(fp)))
if not operations:
print("ERROR: No files found")
sys.exit(1)
print(f"\nUploading {len(operations)} files...")
try:
api.create_commit(
repo_id=SPACE_ID, repo_type="space", operations=operations,
commit_message="feat: real box scores + skip_placeholder + checkpoint/rollback + Brier-dominant fitness",
)
print("Upload OK!")
except Exception as e:
if "404" in str(e) or "not found" in str(e).lower():
print(f"Space not found, creating {SPACE_ID}...")
api.create_repo(repo_id=SPACE_ID, repo_type="space", space_sdk="docker",
space_hardware="cpu-basic", private=False)
api.create_commit(
repo_id=SPACE_ID, repo_type="space", operations=operations,
commit_message="feat: real box scores + skip_placeholder + checkpoint/rollback + Brier-dominant fitness",
)
else:
raise
print("\nConfiguring secrets...")
for key, value in SECRETS.items():
if value:
try:
api.add_space_secret(SPACE_ID, key, value)
print(f" Set {key}")
except Exception as e:
print(f" WARN: {key}: {e}")
else:
print(f" SKIP {key} (empty)")
print("\nRestarting Space...")
try:
api.restart_space(SPACE_ID)
except Exception as e:
print(f" Restart: {e}")
print(f"\nDone! Space: https://lbjlincoln-nomos-nba-quant.hf.space")
print(f"Monitor: https://huggingface.co/spaces/{SPACE_ID}")
if __name__ == "__main__":
main()