| |
| """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 = { |
| |
| "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_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_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", ""), |
| |
| "ODDS_API_KEY": os.environ.get("ODDS_API_KEY", ""), |
| |
| "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", ""), |
| |
| |
| "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", ""), |
| |
| "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", ""), |
| |
| "GH_TOKEN": os.environ.get("GH_TOKEN", ""), |
| "GITHUB_TOKEN": os.environ.get("GITHUB_TOKEN", ""), |
| |
| "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", ""), |
| |
| "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_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) |
|
|
| |
| 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() |
|
|