This is page 1 of 2. Use http://codebase.md/hrgarber/wagyu_mcp_hackathon?page={x} to view the full context. # Directory Structure ``` ├── .github │ ├── hooks │ │ ├── modules │ │ │ ├── api-key-check.sh │ │ │ └── env-check.sh │ │ └── pre-commit │ ├── scripts │ │ └── auto-setup.sh │ └── workflows │ └── auto-setup-hooks.yml ├── .gitignore ├── assets │ └── images │ └── wagyu_ninja.png ├── docs │ ├── README.md │ └── reagan_planning │ ├── meeting with reagan │ │ └── transcript.md │ └── transcript_insights.md ├── LICENSE ├── old │ ├── docs │ │ ├── 2025-01-10_odds_api_v4.md │ │ ├── 2025-01-15_mcp_infra.md │ │ ├── 2025-01-20_api_key_security.md │ │ ├── 2025-02-10_pip_install_fix_plan.md │ │ ├── 2025-02-15_python_odds_api_fix_postmortem.md │ │ └── 2025-02-20_task_context.md │ ├── espn_nonbetting_api │ │ └── espn_api_endpoints.md │ ├── README.md │ └── reagan_planning │ └── mcp_testing_approach.md ├── README.md └── wagyu_sports ├── __init__.py ├── build │ ├── pyproject.toml │ ├── requirements.txt │ └── setup.py ├── config │ ├── .env.example │ └── pytest.ini ├── conftest.py ├── docs │ └── LICENSE ├── examples │ ├── advanced_example.py │ ├── example.py │ ├── fetch_nba_odds.py │ ├── verify_import.py │ └── verify_install.py ├── Makefile ├── mcp_server │ ├── __init__.py │ ├── capture_live_responses.py │ ├── mocks_live │ │ ├── nba_games_live.json │ │ ├── quota_info_live.json │ │ ├── README.md │ │ └── sports_list_live.json │ ├── odds_client_server.py │ ├── odds_client.py │ ├── README.md │ └── test_server.py ├── odds_client.py ├── README.md ├── tests │ ├── README.md │ ├── test_odds_api.py │ ├── test_odds_mcp_server.py │ └── test_simple_mcp.py └── utils.py ``` # Files -------------------------------------------------------------------------------- /wagyu_sports/config/.env.example: -------------------------------------------------------------------------------- ``` # The Odds API Key # Get your API key from https://the-odds-api.com/ ODDS_API_KEY=your_api_key_here ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Node.js node_modules/ .env # Python __pycache__/ *.py[cod] *$py.class .pytest_cache/ .coverage htmlcov/ .tox/ .venv/ venv/ env/ *.egg-info/ dist/ build/ # macOS .DS_Store .AppleDouble .LSOverride Icon ._* .Spotlight-V100 .Trashes .fseventsd .DocumentRevisions-V100 .TemporaryItems .VolumeIcon.icns .com.apple.timemachine.donotpresent # IDE .idea/ .vscode/ *.swp *.swo ``` -------------------------------------------------------------------------------- /old/README.md: -------------------------------------------------------------------------------- ```markdown # Documentation Archive This directory contains historical documentation to track the project's development journey. ## Purpose The purpose of this archive is to chronologically document the development process through markdown and text files. This helps preserve the history of decisions, plans, and implementations without modifying the original documents. ## Structure ``` old/ ├── README.md (this file) └── docs/ (all historical documents) ``` ## Adding Documents 1. When a document becomes historical, copy it to the `docs/` directory 2. Add date prefix: `YYYY-MM-DD_filename.md` 3. Don't modify documents after archiving ## Guidelines - Only add markdown (.md) and text (.txt) files - Always include the date prefix in the format `YYYY-MM-DD_` - Original files should remain in their original locations - This system is intentionally minimal and can evolve as needed All MD and TXT files are welcome. No complex organization required. ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/mocks_live/README.md: -------------------------------------------------------------------------------- ```markdown # Live API Response Captures This directory contains captures of live API responses from the Odds API. These captures can be used for creating updated mock data for testing. ## Using Live Mode The MCP server has been modified to support overriding the test mode setting on a per-call basis. To use the live API (which costs money per call), set the `use_test_mode` parameter to `false` in your tool calls. ### Example Usage ```python # Get sports list using live API <use_mcp_tool> <server_name>wagyu-sports</server_name> <tool_name>get_sports</tool_name> <arguments> { "all_sports": true, "use_test_mode": false } </arguments> </use_mcp_tool> # Get NBA odds using live API <use_mcp_tool> <server_name>wagyu-sports</server_name> <tool_name>get_odds</tool_name> <arguments> { "sport": "basketball_nba", "regions": "us", "markets": "h2h,spreads", "use_test_mode": false } </arguments> </use_mcp_tool> # Get quota information using live API <use_mcp_tool> <server_name>wagyu-sports</server_name> <tool_name>get_quota_info</tool_name> <arguments> { "use_test_mode": false } </arguments> </use_mcp_tool> ``` ## Important Notes 1. **Cost Awareness**: Each live API call costs money. Use sparingly and only when necessary. 2. **Server Restart Required**: After modifying the server code, the MCP server needs to be restarted for changes to take effect. 3. **API Key**: The live mode requires a valid API key, which is already configured in the MCP settings. ## Captured Responses The following live API responses have been captured: - `sports_list_live.json`: List of available sports - `nba_games_live.json`: NBA game odds data - `quota_info_live.json`: API quota information ``` -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- ```markdown # API Documentation Collection This directory contains detailed documentation for various APIs used in our projects. Each markdown file provides comprehensive information about an API's capabilities, endpoints, and integration details. These documents serve as a reference for both human developers and AI assistants when building applications. ## Documentation Notice **Important**: Historical documentation has been moved to the `/old/docs/` directory with date prefixes for better chronological tracking. If you're looking for previous documentation, please check there. ## Documentation Structure Each API documentation file follows a consistent structure: 1. **Overview** - Basic information about the API 2. **Authentication** - How to authenticate with the API 3. **Available Endpoints** - Detailed endpoint documentation 4. **Integration Notes** - Best practices and implementation details 5. **Use Cases** - Common application scenarios 6. **Error Handling** - How to handle API errors 7. **Rate Limiting** - Usage limits and quotas ## Purpose These documentation files are designed to be: 1. **AI-Readable** - Structured in a way that AI can easily parse and understand the capabilities 2. **Comprehensive** - Including all relevant details about the API 3. **Practical** - Focusing on real-world usage and integration 4. **Maintainable** - Easy to update as APIs evolve ## Adding New API Documentation When adding documentation for a new API: 1. Create a new markdown file named appropriately (e.g., `api_name_v1.md`) 2. Follow the consistent documentation structure 3. Include all relevant details about authentication, endpoints, and usage 4. Update this README to include the new API documentation ## Archiving Documentation When documentation becomes historical: 1. Move it to the `/old/docs/` directory 2. Add a date prefix in the format `YYYY-MM-DD_filename.md` 3. See `/old/README.md` for more details on the archiving system ``` -------------------------------------------------------------------------------- /wagyu_sports/README.md: -------------------------------------------------------------------------------- ```markdown # Wagyu Sports A Python client for sports betting data with MCP server integration. ```mermaid graph LR User([Your Code]) --> Client[Wagyu Sports Client] --> API[The Odds API] MCP[MCP Server] --> Client Client --> MCP API --> Client --> User style User fill:#f8f8f8,stroke:#666,stroke-width:1px,color:#000 style Client fill:#4285F4,stroke:#2965C9,stroke-width:2px,color:#fff style API fill:#F5F5F5,stroke:#999,stroke-width:1px,color:#000 style MCP fill:#34A853,stroke:#1E8E3E,stroke-width:2px,color:#fff ``` ## Directory Structure The project has been reorganized for better maintainability: - `build/` - Build-related files (pyproject.toml, requirements.txt, setup.py) - `config/` - Configuration files (.env.example, pytest.ini) - `docs/` - Documentation (LICENSE, README.md) - `examples/` - Example scripts - `mcp_server/` - Model Context Protocol (MCP) server implementation - `tests/` - Test files ## Installation ```bash # Development installation uvx install -e . # User installation uv install wagyu_sports # Set up API key cp config/.env.example config/.env # Edit .env and add your API key from https://the-odds-api.com/ ``` ## Quick Start ```python from wagyu_sports import OddsClient import os from dotenv import load_dotenv # Load API key load_dotenv(dotenv_path="config/.env") api_key = os.getenv("ODDS_API_KEY") # Create client and get sports client = OddsClient(api_key) sports = client.get_sports() print(f"Available sports: {len(sports['data'])}") ``` ## Features - Access to sports betting data endpoints - Track API usage through response headers - Support for all API parameters and options ## Examples See the `examples/` directory for usage patterns: - `examples/example.py`: Basic usage - `examples/advanced_example.py`: Advanced features - `examples/verify_install.py`: Verify installation - `examples/fetch_nba_odds.py`: Fetch NBA odds example - `examples/verify_import.py`: Simple import verification ## Testing The testing suite has been cleaned up and improved for better organization and reliability. Run the tests using pytest: ```bash # Install test dependencies uvx install pytest pytest-asyncio # Run all tests uvx run pytest --rootdir=. -c config/pytest.ini # Run specific test file uvx run pytest tests/test_simple_mcp.py ``` Or use the Makefile: ```bash make test ``` The test suite includes: - **API Client Tests**: Tests for the core Odds API client functionality - **MCP Server Tests**: Tests for the MCP server implementation - **Client-based tests**: Test the full MCP protocol implementation - **Direct tests**: Simpler tests that directly test server methods See the `tests/README.md` file for more details on the testing approach. ## For MCP Server Information See the main README.md file for details on running and configuring the MCP server. ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/README.md: -------------------------------------------------------------------------------- ```markdown # Wagyu Sports MCP Server This directory contains a Model Context Protocol (MCP) server implementation for the Wagyu Sports API. The MCP server wraps the existing `OddsClient` and exposes its functionality through the standardized MCP interface. ## Features - Exposes sports betting data through MCP tools - Supports test mode with mock data for development and testing - Compatible with any MCP client (Claude Desktop, Cline, etc.) ## Available Tools The server exposes the following tools: - `get_sports`: Get a list of available sports - `get_odds`: Get odds for a specific sport - `get_quota_info`: Get API quota information ## Integration with MCP Clients ### Integration with Cline To use this MCP server with Cline: 1. Add the server to Cline's MCP settings file located at: - macOS: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` - Windows: `%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json` 2. Add the following configuration: ```json { "mcpServers": { "wagyu-sports": { "command": "python", "args": ["/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py"], "env": { "ODDS_API_KEY": "your_api_key" }, "disabled": false, "autoApprove": [] } } } ``` 3. Replace `/path/to/wagyu_mcp_hackathon` with the actual path to your project 4. Replace `your_api_key` with your actual API key from [The Odds API](https://the-odds-api.com/) 5. Restart Cline ### Integration with Claude Desktop To use this MCP server with Claude Desktop: 1. Add the server to Claude Desktop's configuration file located at: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 2. Add the following configuration: ```json { "mcpServers": { "wagyu-sports": { "command": "python", "args": ["/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py"], "env": { "ODDS_API_KEY": "your_api_key" }, "disabled": false, "autoApprove": [] } } } ``` 3. Replace `/path/to/wagyu_mcp_hackathon` with the actual path to your project 4. Replace `your_api_key` with your actual API key from [The Odds API](https://the-odds-api.com/) 5. Restart Claude Desktop ## Test Mode The server can be run in test mode by adding the `--test-mode` flag to the command: ```json "args": ["/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"] ``` In test mode, the server uses mock data from the `mocks_live/` directory instead of making real API calls, which: - Doesn't require an API key - Doesn't consume your API quota - Works offline ``` -------------------------------------------------------------------------------- /wagyu_sports/tests/README.md: -------------------------------------------------------------------------------- ```markdown # Wagyu Sports MCP Tests This directory contains tests for the Wagyu Sports MCP server implementation. ## Test Files - `test_odds_api.py` - Tests for the core Odds API client - `test_odds_mcp_server.py` - Tests for the MCP server implementation - `test_simple_mcp.py` - Simple direct tests for the MCP server functionality ## How to Run the Tests The project uses pytest for running tests. The configuration is in `wagyu_sports/config/pytest.ini`. ### Using pytest directly ```bash # Run all tests from the wagyu_sports directory cd wagyu_sports pytest # Run specific test file pytest tests/test_odds_mcp_server.py # Run with verbose output (already default in pytest.ini) pytest tests/test_odds_mcp_server.py # Run a specific test function pytest tests/test_odds_mcp_server.py::test_get_sports ``` ### Environment Setup The tests use the `test_mode=True` flag to run with mock data instead of making real API calls. This is handled automatically in the test code. ### Pytest Configuration The project's pytest.ini configuration: ```ini [pytest] testpaths = tests python_files = test_*.py python_functions = test_* addopts = -v ``` This configuration automatically: - Looks for tests in the `tests` directory - Runs files that start with `test_` - Runs functions that start with `test_` - Uses verbose output by default ## How to Add New Tests ### Test Approaches There are two main approaches to testing the MCP server: 1. **Client-based testing** (in `test_odds_mcp_server.py`): - Uses the MCP client session to test the server through the MCP protocol - Tests the full protocol implementation - Good for integration testing 2. **Direct testing** (in `test_simple_mcp.py`): - Tests the server methods directly - Faster and simpler for testing core functionality - Good for unit testing ### Client-Based Test Structure Client-based tests for the MCP server should follow this pattern: ```python @pytest.mark.anyio async def test_your_feature(): """Test description""" # Initialize server in test mode server = OddsMcpServer(test_mode=True) # Create client session async with client_session(server.server) as client: # Call the tool result = await client.call_tool("tool_name", {"param": "value"}) # Assert results assert len(result.content) > 0 content = result.content[0] assert isinstance(content, TextContent) # Add specific assertions for your test case assert "expected_value" in content.text ``` ### Direct Test Structure Direct tests access the server methods directly: ```python @pytest.mark.asyncio async def test_direct_method(): """Test description""" # Initialize server in test mode server = OddsMcpServer(test_mode=True) # Test method directly mock_data = await server._get_mock_data("data_file.json") # Parse and verify the response data = json.loads(mock_data) assert "expected_key" in data # Add more assertions... ``` ### Testing New Tools When adding a new tool to the MCP server: 1. Add the tool implementation to `odds_client_server.py` 2. Create a mock data file in `wagyu_sports/mcp_server/mocks_live/` if needed 3. Add a test function in `test_odds_mcp_server.py` following the pattern above 4. Ensure your test verifies both the structure and content of the response ### Testing with Different Parameters To test a tool with different parameters: ```python @pytest.mark.anyio async def test_tool_with_params(): server = OddsMcpServer(test_mode=True) async with client_session(server.server) as client: result = await client.call_tool( "tool_name", { "param1": "value1", "param2": "value2" } ) # Assertions... ``` ### Testing Resources To test MCP resources: ```python @pytest.mark.anyio async def test_resource(): server = OddsMcpServer(test_mode=True) async with client_session(server.server) as client: # List resources resources = await client.list_resources() # Read a resource result = await client.read_resource("resource://uri") # Assertions... ``` ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Wagyu Sports MCP Server <p align="center"> <img src="assets/images/wagyu_ninja.png" width="250" alt="Wagyu Sports Logo"> </p> A Model Context Protocol (MCP) server for sports betting data, providing access to The Odds API through Claude and other MCP-compatible AI assistants. ```mermaid graph LR Claude([Claude]) --> MCP[Wagyu Sports MCP] MCP --> API[The Odds API] API --> MCP --> Claude style Claude fill:#f8f8f8,stroke:#666,stroke-width:1px,color:#000 style MCP fill:#34A853,stroke:#1E8E3E,stroke-width:2px,color:#fff style API fill:#F5F5F5,stroke:#999,stroke-width:1px,color:#000 ``` ## Quick Setup 1. **Clone the repository**: ```bash # Clone the repository git clone https://github.com/your-username/wagyu_mcp_hackathon.git cd wagyu_mcp_hackathon ``` 2. **Install the package**: ```bash # Using pip (recommended) pip install -e . # Or using uv (alternative) uv install -e . ``` 3. **Add to your MCP configuration**: For Cline, add to `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`: ```json { "mcpServers": { "wagyu-sports": { "command": "python", "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"], "env": { "ODDS_API_KEY": "your_api_key_here" }, "disabled": false, "autoApprove": [] } } } ``` For Claude Desktop, add to `~/Library/Application Support/Claude/claude_desktop_config.json`: ```json { "mcpServers": { "wagyu-sports": { "command": "python", "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"], "env": { "ODDS_API_KEY": "your_api_key_here" }, "disabled": false, "autoApprove": [] } } } ``` > **IMPORTANT**: Replace `/absolute/path/to/wagyu_mcp_hackathon` with the actual full path to your repository. For example: `/Users/john/Documents/hackathon/wagyu_mcp_hackathon`. 4. **Get an API key** from [The Odds API](https://the-odds-api.com/) and replace `your_api_key_here` in the configuration. 5. **Restart your MCP client** (Cline or Claude Desktop). ## Available Tools The MCP server provides the following tools: - `get_sports`: Get a list of available sports - `get_odds`: Get odds for a specific sport - `get_quota_info`: Get API quota information ## Test Mode vs. Real Mode ### Test Mode (Recommended for Getting Started) Test mode uses mock data instead of making real API calls. This is useful for: - Development and testing without API rate limits - Demos and presentations - Learning how to use the MCP server To use test mode: 1. Set `--test-mode` in your MCP configuration (as shown in the Quick Setup) 2. No API key is required 3. The server will return consistent mock data for all requests Example configuration for test mode: ```json "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py", "--test-mode"] ``` ### Real Mode Real mode makes actual API calls to The Odds API. This is necessary for: - Getting real-time sports betting data - Production applications - Accurate odds information To use real mode: 1. Remove the `--test-mode` flag from your MCP configuration 2. Provide a valid API key from The Odds API 3. Be aware of API rate limits (typically 500 requests per month for free tier) Example configuration for real mode: ```json "args": ["/absolute/path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py"], "env": { "ODDS_API_KEY": "your_actual_api_key_here" } ``` You can also run the server directly with: ```bash python /path/to/wagyu_mcp_hackathon/wagyu_sports/mcp_server/test_server.py --api-key=your_api_key_here ``` ## Development For development and testing: ```bash # Clone the repository git clone https://github.com/your-username/wagyu_mcp_hackathon.git cd wagyu_mcp_hackathon # Install in development mode pip install -e . # Run tests python -m pytest wagyu_sports/tests # Run the server directly (test mode) python wagyu_sports/mcp_server/test_server.py --test-mode # Run the server directly (real mode) python wagyu_sports/mcp_server/test_server.py --api-key=your_api_key_here ``` > **Note**: This repository includes a post-commit Git hook that automatically cleans up Python cache files (`__pycache__`, `.pyc`, `.pyo`, `.pyd`) and `.pytest_cache` directories after each commit. ## Project Structure - `wagyu_sports/mcp_server/` - MCP server implementation - `wagyu_sports/tests/` - Test files - `wagyu_sports/examples/` - Example scripts ## For More Information See the `wagyu_sports/README.md` file for details on using the Python client directly. ``` -------------------------------------------------------------------------------- /wagyu_sports/config/pytest.ini: -------------------------------------------------------------------------------- ``` [pytest] testpaths = tests python_files = test_*.py python_functions = test_* addopts = -v ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/mocks_live/quota_info_live.json: -------------------------------------------------------------------------------- ```json { "_metadata": { "captured_at": "2025-03-03T01:50:08-08:00", "description": "Live data captured from the Odds API", "tool": "get_quota_info", "parameters": {} }, "remaining_requests": "488", "used_requests": "12" } ``` -------------------------------------------------------------------------------- /wagyu_sports/conftest.py: -------------------------------------------------------------------------------- ```python """ Configuration file for pytest. This file sets up the Python path for tests to ensure imports work correctly. """ import os import sys # Add the project root to the Python path sys.path.insert(0, os.path.abspath(os.path.dirname(__file__))) ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/__init__.py: -------------------------------------------------------------------------------- ```python """ Wagyu Sports MCP Server This module provides an MCP server implementation for the Wagyu Sports API. """ try: # When imported as a package from .odds_client_server import OddsMcpServer except ImportError: # When run directly from odds_client_server import OddsMcpServer __all__ = ["OddsMcpServer"] ``` -------------------------------------------------------------------------------- /wagyu_sports/__init__.py: -------------------------------------------------------------------------------- ```python """ Wagyu Sports Package This package provides a client for sports betting data, allowing users to fetch sports betting data including live odds, scores, and event information. """ from wagyu_sports.odds_client import OddsClient from wagyu_sports.utils import get_next_test_number, save_response, test_wagyu_sports __all__ = ['OddsClient', 'get_next_test_number', 'save_response', 'test_wagyu_sports'] ``` -------------------------------------------------------------------------------- /wagyu_sports/examples/verify_import.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Simple verification script to check imports from the Wagyu Sports client. This script attempts to import the OddsClient class and create an instance, which verifies that the package is installed correctly and can be imported. """ try: print("Attempting to import OddsClient...") from wagyu_sports import OddsClient print("Successfully imported OddsClient!") # Try creating an instance client = OddsClient("test_key") print("Successfully created OddsClient instance!") except ImportError as e: print(f"Import Error: {e}") except Exception as e: print(f"Other Error: {e}") ``` -------------------------------------------------------------------------------- /.github/hooks/modules/env-check.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Module to check for .env files in commits # Prevents accidentally committing environment files with sensitive data # Check for .env files in the staged changes if git diff --cached --name-only | grep -q "\.env$"; then echo "ERROR: Attempting to commit .env file." echo "These files typically contain sensitive information like API keys." echo "Add them to .gitignore instead and use .env.example as a template." echo "" echo "Offending files:" git diff --cached --name-only | grep "\.env$" exit 1 fi # Also check for files that might be .env files with different extensions if git diff --cached --name-only | grep -q "env\.\|\.env\."; then echo "WARNING: Possible environment file detected." echo "Please verify these files don't contain sensitive information:" git diff --cached --name-only | grep "env\.\|\.env\." echo "" echo "Continue with commit? (y/n)" read -r response if [[ "$response" != "y" ]]; then exit 1 fi fi exit 0 ``` -------------------------------------------------------------------------------- /.github/hooks/modules/api-key-check.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Module to check for API keys in commits # Scans for common API key patterns to prevent accidental exposure # Define patterns to check for # Add more patterns as needed for different types of API keys PATTERNS=( # The Odds API key pattern (alphanumeric, typically 32 chars) "ODDS_API_KEY=[a-zA-Z0-9]{32}" # Generic API key patterns "api[_-]key[=\"':= ][a-zA-Z0-9]" "apikey[=\"':= ][a-zA-Z0-9]" "key[=\"':= ][a-zA-Z0-9]{32}" "secret[=\"':= ][a-zA-Z0-9]" "password[=\"':= ][a-zA-Z0-9]" "token[=\"':= ][a-zA-Z0-9]" ) # Check staged files for API key patterns for pattern in "${PATTERNS[@]}"; do # Use git grep to search staged changes matches=$(git diff --cached -U0 | grep -i -E "$pattern" || true) if [ -n "$matches" ]; then echo "ERROR: Potential API key or sensitive data found in commit." echo "Pattern matched: $pattern" echo "" echo "Please remove the sensitive data and try again." echo "Consider using environment variables or a secure vault." exit 1 fi done exit 0 ``` -------------------------------------------------------------------------------- /.github/workflows/auto-setup-hooks.yml: -------------------------------------------------------------------------------- ```yaml name: Auto Setup Git Hooks on: push: branches: [ main, master ] pull_request: branches: [ main, master ] workflow_dispatch: # Allows manual triggering jobs: setup-hooks: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: Configure Git Hooks run: | chmod +x .github/scripts/auto-setup.sh .github/scripts/auto-setup.sh - name: Verify Hooks Configuration run: | echo "Verifying hooks configuration..." if [ "$(git config --get core.hooksPath)" = ".github/hooks" ]; then echo "✅ Hooks configured successfully" else echo "❌ Hooks configuration failed" exit 1 fi - name: Check for .env Files run: | echo "Checking for .env files..." if git ls-files | grep -q "\.env$"; then echo "⚠️ Warning: .env files found in repository" git ls-files | grep "\.env$" else echo "✅ No .env files found in repository" fi ``` -------------------------------------------------------------------------------- /.github/scripts/auto-setup.sh: -------------------------------------------------------------------------------- ```bash #!/bin/bash # Auto-setup script for git hooks in local development # This script is automatically triggered by GitHub Actions in CI/CD # but can also be run manually for local development # Exit on error set -e # Get the root directory of the git repository REPO_ROOT=$(git rev-parse --show-toplevel) cd "$REPO_ROOT" echo "🔒 Setting up automatic git hooks..." # Configure git to use hooks from .github/hooks git config --local core.hooksPath .github/hooks echo "✓ Configured git to use hooks from .github/hooks" # Make sure all hook scripts are executable chmod +x .github/hooks/pre-commit echo "✓ Made pre-commit hook executable" # Make all module scripts executable MODULE_COUNT=0 for module in .github/hooks/modules/*.sh; do if [ -f "$module" ]; then chmod +x "$module" echo "✓ Made $(basename "$module") executable" MODULE_COUNT=$((MODULE_COUNT+1)) fi done echo "" echo "✅ Git hooks configured automatically!" echo "The following hooks are now active:" echo "- pre-commit: Prevents committing sensitive information like API keys" echo "" echo "Modules configured ($MODULE_COUNT total):" echo "- env-check.sh: Prevents committing .env files" echo "- api-key-check.sh: Scans for API key patterns in code" echo "" echo "Your repository is now protected against accidental API key commits!" echo "This configuration will persist across all branches." ``` -------------------------------------------------------------------------------- /old/docs/2025-02-15_python_odds_api_fix_postmortem.md: -------------------------------------------------------------------------------- ```markdown # Python Odds API Fix Post-Mortem ## Issues & Fixes ### Issues Identified 1. **Deprecation Warning**: Legacy editable install warning during pip installation 2. **Import Error**: Package couldn't be imported after installation 3. **Package Structure**: Flat structure not following Python packaging conventions ### Implemented Fixes 1. Added `pyproject.toml` to fix deprecation warning 2. Restructured package to use proper nested directory: ``` wagyu_sports/ ├── pyproject.toml ├── setup.py └── wagyu_sports/ <-- New subdirectory ├── __init__.py ├── odds_client.py └── utils.py ``` 3. Updated root `__init__.py` to import from subdirectory ## Divergence from Original Plan The implementation followed the original plan with two key differences: 1. **Minimal pyproject.toml**: Used minimal configuration rather than comprehensive metadata 2. **Kept Original Files**: Didn't move test files to a separate tests directory to minimize changes These decisions were made to implement the most critical fixes with minimal changes to the codebase. ## Installation Instructions ```bash # Clone the repository git clone <repository-url> cd wagyu_mcp_hackathon/api_test/wagyu_sports # Install the package pip install -e . # Set up API key cp .env.example .env # Edit .env and add your API key # Verify installation python run_test.py ``` ## Usage Example ```python from wagyu_sports import OddsClient from dotenv import load_dotenv import os # Load API key load_dotenv() api_key = os.getenv("ODDS_API_KEY") # Create client and fetch sports client = OddsClient(api_key) sports = client.get_sports() print(f"Found {len(sports['data'])} sports") ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/test_server.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Test script for the Wagyu Sports MCP Server. This script initializes and runs the MCP server in test mode, which uses mock data instead of making real API calls. """ import asyncio import os import sys import argparse from pathlib import Path # Import directly from the current directory from odds_client_server import OddsMcpServer async def main(): """Run the MCP server.""" # Parse command line arguments parser = argparse.ArgumentParser(description="Wagyu Sports MCP Server") parser.add_argument("--test-mode", action="store_true", help="Use mock data instead of real API calls") args = parser.parse_args() # Determine if we should use test mode test_mode = args.test_mode if test_mode: print("Starting Wagyu Sports MCP Server in test mode...") print("This will use mock data instead of making real API calls.") else: print("Starting Wagyu Sports MCP Server in live mode...") print("This will make real API calls that cost money.") print() # Initialize server server = OddsMcpServer(test_mode=test_mode) # Run the server await server.run() if __name__ == "__main__": print("=" * 80) print("Wagyu Sports MCP Server Test") print("=" * 80) print() print("To test this server with mcp_inspector:") print("1. In another terminal, run:") print(" npx @modelcontextprotocol/inspector python wagyu_sports/mcp_server/test_server.py") print() print("2. The inspector will connect to this server and allow you to:") print(" - View and test available tools") print(" - See the results of tool calls") print(" - Monitor server logs") print() print("3. Try calling these tools:") print(" - get_sports") print(" - get_odds (with sport='basketball_nba')") print(" - get_quota_info") print() print("=" * 80) print() try: asyncio.run(main()) except KeyboardInterrupt: print("\nServer stopped by user.") ``` -------------------------------------------------------------------------------- /wagyu_sports/examples/fetch_nba_odds.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Example script for fetching NBA odds using the Wagyu Sports client. This script demonstrates how to use the Wagyu Sports client to fetch NBA odds and save the response to a file. """ import os import json from dotenv import load_dotenv import requests def fetch_nba_odds(): """ Fetch NBA odds from the sports data API. This function: 1. Loads the API key from environment variables 2. Makes a direct request to the sports data API for NBA odds 3. Saves the response to a file Returns: str: Path to the saved file """ # Load environment variables load_dotenv(dotenv_path="config/.env") # Get API key api_key = os.getenv("ODDS_API_KEY") if not api_key: print("Error: ODDS_API_KEY not found in environment variables") print("Please copy config/.env.example to config/.env and add your API key: ODDS_API_KEY=your_api_key_here") return try: # Get NBA odds response = requests.get( 'https://api.the-odds-api.com/v4/sports/basketball_nba/odds', params={ 'apiKey': api_key, 'regions': 'us', 'markets': 'h2h,spreads', 'oddsFormat': 'american' } ) # Check for successful response response.raise_for_status() # Prepare output output = { 'odds': response.json(), 'remainingRequests': response.headers.get('x-requests-remaining') } # Write response to file with open('nba_odds.json', 'w') as f: json.dump(output, f, indent=2) print('NBA odds written to nba_odds.json') print(f'Remaining requests: {output["remainingRequests"]}') return 'nba_odds.json' except requests.exceptions.RequestException as e: print(f'Error: {e.response.json() if hasattr(e, "response") else str(e)}') return None if __name__ == "__main__": # Run the example if this file is executed directly fetch_nba_odds() ``` -------------------------------------------------------------------------------- /old/docs/2025-02-20_task_context.md: -------------------------------------------------------------------------------- ```markdown # Python Odds API Fix Task Context ## Current State - Branch: `fix/python_api` - Working Directory: `/Users/brandonbutterwick/Documents/hackathon/wagyu_mcp_hackathon` - Project Location: `api_test/wagyu_sports/` ## Background Context 1. The Python Odds API package is currently experiencing installation and import issues 2. A friend's PC was able to run the package successfully, indicating environment-specific problems 3. The package uses a conda environment named 'sports' for development 4. Current dependencies are managed through requirements.txt: - requests>=2.25.0 - python-dotenv>=0.15.0 ## Known Issues 1. Package fails to import when running `run_test.py` 2. Legacy editable install warning appears during pip installation 3. Import paths are not resolving correctly 4. Package structure needs modernization ## Environment Setup 1. Conda environment 'sports' is being used 2. Dependencies have been installed in the conda environment 3. The .env file contains the ODDS_API_KEY for testing ## Recent Changes 1. Created detailed implementation plan in `docs/pip_install_fix_plan.md` 2. No structural changes have been made yet 3. Original package structure and files remain intact ## Next Action Items 1. Create new directory structure as outlined in the plan 2. Implement modern Python packaging with pyproject.toml 3. Update package configuration in setup.py 4. Reorganize test files into dedicated test directory ## Important Files - `run_test.py`: Main test script showing import issues - `odds_client.py`: Core client implementation - `setup.py`: Package configuration (needs modernization) - `.env`: Contains API key for testing - `requirements.txt`: Current dependency specifications ## Testing Notes 1. Test script requires ODDS_API_KEY environment variable 2. Current test failure is at import level, not API functionality 3. Package should be tested in both regular and editable install modes ## Additional Context 1. Package is intended to be pip-installable for end users 2. Need to maintain compatibility with existing API usage patterns 3. Modern Python packaging practices should be followed 4. Documentation needs to be updated after fixes This context document should help Roo pick up the task and continue with the implementation phase of the pip installation fixes. ``` -------------------------------------------------------------------------------- /wagyu_sports/tests/test_simple_mcp.py: -------------------------------------------------------------------------------- ```python """Simple tests for Wagyu Sports MCP server following the Python MCP SDK example pattern""" import json import os import sys import pytest from unittest.mock import MagicMock, patch # Import directly from the module from wagyu_sports.mcp_server.odds_client_server import OddsMcpServer @pytest.mark.asyncio async def test_simple_get_sports(): """Test the basic functionality of the get_sports tool""" # Create an instance of the server in test mode server = OddsMcpServer(test_mode=True) # Test the _get_mock_data method directly mock_data = await server._get_mock_data("sports_list.json") # Parse and verify the response data = json.loads(mock_data) assert "data" in data assert isinstance(data["data"], list) assert any(sport["key"] == "basketball_nba" for sport in data["data"]) @pytest.mark.asyncio async def test_simple_get_odds(): """Test the basic functionality of the get_odds tool""" # Create an instance of the server in test mode server = OddsMcpServer(test_mode=True) # Test the _get_mock_data method directly for NBA games mock_data = await server._get_mock_data("nba_games.json") # Parse and verify the response data = json.loads(mock_data) assert "data" in data assert isinstance(data["data"], list) assert len(data["data"]) > 0 # Check the first game first_game = data["data"][0] assert "sport_key" in first_game assert first_game["sport_key"] == "basketball_nba" assert "home_team" in first_game assert "away_team" in first_game @pytest.mark.asyncio async def test_simple_get_quota_info(): """Test the basic functionality of the get_quota_info tool""" # Create an instance of the server in test mode server = OddsMcpServer(test_mode=True) # In test mode, get_quota_info returns a fixed response # We can construct this manually to test the structure expected_data = { "remaining_requests": "100", "used_requests": "50" } # Create a response similar to what the tool would return quota_response = json.dumps(expected_data, indent=2) quota_data = json.loads(quota_response) # Verify the expected structure assert "remaining_requests" in quota_data assert "used_requests" in quota_data if __name__ == "__main__": pytest.main(["-xvs", __file__]) ``` -------------------------------------------------------------------------------- /old/docs/2025-01-20_api_key_security.md: -------------------------------------------------------------------------------- ```markdown # API Key Security ## Current Status After investigating the repository, we've confirmed that: 1. The `.env` file containing your API key is **not currently tracked by git** 2. The API key `22c87ba01f0fecd6a3acbf114ebcb940` does **not appear in the git history** 3. The `.gitignore` file is correctly configured to ignore `.env` files This means your API key is currently secure and has not been exposed in the git history. ## Implemented Security Measures We've created a new branch `feature/git_action_.env` with git hooks to prevent accidentally committing API keys or `.env` files in the future: 1. **Pre-commit hook**: Automatically runs checks before each commit 2. **Environment file check**: Prevents committing `.env` files 3. **API key pattern detection**: Scans for API keys in staged changes ## Fully Automated Git Hooks The git hooks are now **fully automated** using GitHub Actions: 1. **No manual setup required** - hooks are configured automatically by CI/CD 2. **Works across all branches** - protection is consistent everywhere 3. **Automatic for all developers** - no need to run setup scripts ### For Local Development If you're working locally without GitHub Actions, you can run: ```bash .github/scripts/auto-setup.sh ``` This will configure Git to use the hooks from `.github/hooks` instead of the default `.git/hooks` directory, making the hooks a default part of the repository. ### CI/CD Integration The GitHub Actions workflow in `.github/workflows/auto-setup-hooks.yml` automatically: 1. Sets up the hooks for all developers 2. Verifies the hooks are configured correctly 3. Checks for any .env files that might have been committed ## Best Practices for API Key Security 1. **Never commit `.env` files**: Store sensitive information in environment variables 2. **Use `.env.example` files**: Provide templates without real values 3. **Rotate API keys regularly**: Change keys periodically, especially after suspected exposure 4. **Use different keys**: Use separate keys for development, testing, and production 5. **Set up access controls**: Limit who has access to production API keys ## What to Do If You Accidentally Commit an API Key If you accidentally commit an API key: 1. **Rotate the key immediately**: Generate a new key from the service provider 2. **Remove from git history**: Use tools like `git-filter-repo` to remove sensitive data 3. **Force push**: Update the remote repository with the cleaned history 4. **Notify team members**: Ensure everyone pulls the updated history ## Additional Resources - [Git documentation on .gitignore](https://git-scm.com/docs/gitignore) - [GitHub's guide on removing sensitive data](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) ``` -------------------------------------------------------------------------------- /wagyu_sports/examples/verify_install.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Verification script to check the installation and functionality of the Wagyu Sports client. This script: 1. Checks if the required dependencies are installed 2. Verifies that the API key is set in the environment 3. Runs a simple test to fetch sports data """ import os import sys import importlib.util def check_dependency(package_name): """Check if a Python package is installed.""" spec = importlib.util.find_spec(package_name) if spec is None: print(f"Error: {package_name} is not installed.") print(f"Please install it using: pip install {package_name}") return False return True def main(): """Main function to run tests.""" # Check dependencies dependencies = ["requests", "dotenv"] all_installed = True for dep in dependencies: if not check_dependency(dep): all_installed = False if not all_installed: print("\nPlease install all required dependencies and try again.") print("You can install them using: pip install -r build/requirements.txt") return # Now that we know dependencies are installed, import them from dotenv import load_dotenv # Check for API key load_dotenv(dotenv_path="config/.env") api_key = os.getenv("ODDS_API_KEY") if not api_key: print("Error: ODDS_API_KEY not found in environment variables.") print("Please copy config/.env.example to .env and add your API key: ODDS_API_KEY=your_api_key_here") return # Try to import the client try: from wagyu_sports import OddsClient # Create client client = OddsClient(api_key) # Test getting sports print("Testing API connection by fetching available sports...") response = client.get_sports() # Check response if "data" in response and isinstance(response["data"], list): sport_count = len(response["data"]) print(f"Success! Fetched {sport_count} available sports.") print(f"Remaining API requests: {response['headers']['x-requests-remaining']}") # Show a few sports as example if sport_count > 0: print("\nSample sports:") for sport in response["data"][:3]: # Show first 3 sports print(f"- {sport.get('title', 'Unknown')}: {sport.get('key', 'Unknown')}") print("\nThe Wagyu Sports client is working correctly!") else: print("Error: Unexpected response format from the API.") print("Response:", response) except ImportError: print("Error: Could not import the Wagyu Sports client.") print("Make sure the package is installed correctly.") except Exception as e: print(f"Error during API test: {e}") if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /old/reagan_planning/mcp_testing_approach.md: -------------------------------------------------------------------------------- ```markdown # MCP Testing Approach: Mock Response System ## Overview This document outlines a testing approach for sports betting MCPs that uses mock data instead of live API calls. This method allows for consistent, repeatable testing without API rate limits or dependencies on current sporting events. ## Core Concept Create a layered MCP architecture with a base "Odds MCP" that other specialized MCPs can use. This base MCP can operate in either live mode (real API calls) or test mode (predetermined mock responses). ## Implementation Steps ### 1. Create Mock Data Files ``` /mocks/ sports_list.json # Available sports nba_games.json # List of NBA games game_odds_caesars.json # Single game odds from default book game_odds_all_books.json # Comparison across books futures_odds.json # Championship futures odds ``` ### 2. Build a Test-Ready Base MCP ```python # Base MCP with test/live mode toggle class OddsMCP: def __init__(self, api_key=None, test_mode=False): self.api_key = api_key self.test_mode = test_mode def get_data(self, endpoint, params=None): if self.test_mode: # Return appropriate mock based on endpoint and params return self._get_mock_response(endpoint, params) else: # Make actual API call return self._call_api(endpoint, params) ``` ### 3. Create Specialized MCPs Using the Base ```python # Example of a specialized MCP class GameBrowserMCP: def __init__(self, odds_mcp): self.odds_mcp = odds_mcp def get_available_sports(self): # Uses the base MCP, which could be in test mode return self.odds_mcp.get_data("sports") def get_games_for_sport(self, sport): return self.odds_mcp.get_data("games", {"sport": sport}) ``` ## Benefits 1. **Controlled Testing Environment**: Predictable data for each test scenario 2. **Fast Execution**: No network delays or API rate limits 3. **Comprehensive Coverage**: Test edge cases and rare situations 4. **Isolated Components**: Test each MCP layer independently 5. **Repeatable Results**: Tests produce consistent output ## Mock Data Example ```json // Example mock for a game odds response { "game": { "home_team": "Lakers", "away_team": "Grizzlies", "commence_time": "2025-03-02T19:30:00Z" }, "odds": { "caesars": { "spread": {"home": -4.5, "away": 4.5, "odds": -110}, "moneyline": {"home": -190, "away": 160}, "totals": {"over": 224.5, "under": 224.5, "odds": -110} } } } ``` ## Test Flow 1. Initialize test versions of MCPs with `test_mode=True` 2. Run through user scenarios from the test_scenarios.md document 3. Compare MCP outputs to expected responses 4. Toggle to live mode for final verification with real data This approach provides a stable foundation for development while keeping the option to test against live data when needed. ``` -------------------------------------------------------------------------------- /docs/reagan_planning/transcript_insights.md: -------------------------------------------------------------------------------- ```markdown # Sports Betting AI - Transcript Insights This document summarizes key insights from the conversation with Reagan about sports betting user needs and preferences. ## 1. User Stories / Scenarios ### Scenario 1: The Value Shopper **User Need:** "I want to place a bet on Loyola Marymount at the highest possible odds" **Action:** Compares odds across multiple sportsbooks for a specific selection **Output Example:** "DraftKings offers Loyola Marymount at +925, while FanDuel has them at +900" ### Scenario 2: The Casual Game Night Better **User Need:** "I'm watching the Lakers game with friends and want to place a quick bet" **Action:** Looks up basic odds for a specific game **Output Example:** "Lakers vs. Grizzlies: Lakers -4.5, O/U 224.5, Lakers ML -190" ### Scenario 3: The Trend Spotter **User Need:** "I want to see if there's unusual line movement on any games tonight" **Action:** Looks for significant recent changes in betting lines **Output Example:** "The White Sox line has moved from +300 to +375 in the last hour despite 60% of bets being placed on them" ## 2. Data Points to Track | Data Category | Priority | Examples | |---------------|----------|----------| | Basic Odds | High | Spread, Money Line, Over/Under | | Book Comparison | High | Same bet across multiple sportsbooks | | Line Movement | Medium | Changes from opening line to current | | Bet Distribution | Medium | % of bets vs % of money on each side | | Futures | Medium | Long-term bets with higher variation across books | ## 3. User Experience Flow 1. **Initial Query** - User asks: "Show me the line for Lakers-Grizzlies" 2. **Primary Response** - Default sportsbook odds (Caesar's suggested) - Standard format: Spread, O/U, Money Line 3. **Secondary Options** - "Would you like to see odds from other sportsbooks?" - "Would you like to see betting trends for this game?" 4. **Advanced Data (On Request)** - Line movement information - Bet distribution (money vs. bets) ## 4. Implementation Priorities 1. Basic odds retrieval for major US sports (single sportsbook) 2. Multiple sportsbook comparison for same game/bet 3. Format optimization for AI chat interface 4. Line movement indicators (if time permits) 5. Bet distribution data (if available in your API) ## 5. Key Insights from Reagan - **Futures bets** have the most variation between sportsbooks and offer the best opportunity for value shopping - **Line movement** is a key indicator that bettors look for to identify potential value - **Bet distribution** (% of money vs. % of bets) can signal "sharp" money and is valuable information - **Layered information** is important - start with basic odds, then offer more detailed information - **User preferences** for which sportsbooks to display is important - **Anomalies** in odds or betting patterns can either attract or repel bettors depending on their strategy - **Default display** should be simple (spread, money line, over/under) with options to see more ``` -------------------------------------------------------------------------------- /old/docs/2025-02-10_pip_install_fix_plan.md: -------------------------------------------------------------------------------- ```markdown # Plan for Making Python Odds API Properly Pip-Installable After analyzing the current state of the Python Odds API package, I've identified several issues that prevent it from being properly installed and imported. Here's a comprehensive plan to fix these issues: ## 1. Diagnose Current Issues Currently, the package can be installed with `pip install -e .` but fails with an import error when running `run_test.py`. The main issues appear to be: - Package structure is not following modern Python packaging best practices - The installation process is working but the import path is not resolving correctly - The setup.py file needs modernization - There's a deprecation warning about legacy editable installs ## 2. Package Restructuring ### 2.1. Create a Modern Package Structure ``` api_test/wagyu_sports/ ├── pyproject.toml # New file for modern Python packaging ├── setup.py # Updated version ├── README.md # Existing file ├── requirements.txt # Existing file ├── wagyu_sports/ # Package directory (new or renamed) │ ├── __init__.py # Updated to expose the correct modules │ ├── odds_client.py # Main module │ └── utils.py # Utilities module └── tests/ # Separate test directory (new) ├── __init__.py ├── run_test.py # Moved from root └── test_odds_client.py # Existing test ``` ## 3. Update Package Files ### 3.1. Create a Modern pyproject.toml Create a new `pyproject.toml` file to use modern Python packaging tools (PEP 517/518) with: - Build system specification - Project metadata - Dependencies - Development dependencies ### 3.2. Update setup.py Modernize the existing setup.py: - Ensure package discovery works correctly - Update metadata - Fix URL and author information - Make sure dependencies match requirements.txt ### 3.3. Update __init__.py Ensure the package's `__init__.py` correctly exposes the intended classes and functions. ## 4. Fix Import Paths ### 4.1. Update Import Statements Ensure all import statements in the package are consistent with the new structure. ### 4.2. Update Test Scripts Modify test scripts to use the correct import paths. ## 5. Testing Plan ### 5.1. Test Installation Test the package installation in different scenarios: - Regular install: `pip install .` - Editable install: `pip install -e .` - Install from source distribution: `pip install dist/*.tar.gz` ### 5.2. Test Imports Verify imports work correctly: - In different Python environments - From different directories - With different import statements ### 5.3. Functional Testing Run the test scripts to ensure the client functions correctly: - Basic API functionality - Error handling - Edge cases ## 6. Documentation Updates ### 6.1. Update README Update installation and usage instructions in the README.md file. ### 6.2. Add Examples Provide clear examples of how to install and use the package. ## 7. Cleanup Remove any temporary files or old structures that are no longer needed. ## Implementation Timeline 1. Package restructuring (30 minutes) 2. Update configuration files (20 minutes) 3. Fix import paths (15 minutes) 4. Testing (20 minutes) 5. Documentation updates (15 minutes) ## Next Steps 1. Create the new directory structure 2. Create pyproject.toml 3. Update setup.py with modern configuration 4. Move and update test files 5. Test the installation process ``` -------------------------------------------------------------------------------- /wagyu_sports/examples/example.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Example script demonstrating how to use the Wagyu Sports client. """ import os from dotenv import load_dotenv from wagyu_sports import OddsClient def main(): """ Main function demonstrating the use of the Wagyu Sports client. """ # Load environment variables load_dotenv(dotenv_path="config/.env") # Get API key api_key = os.getenv("ODDS_API_KEY") if not api_key: print("Error: ODDS_API_KEY not found in environment variables") print("Please copy config/.env.example to config/.env and add your API key: ODDS_API_KEY=your_api_key_here") return # Create client client = OddsClient(api_key) # Get available sports try: print("Fetching available sports...") sports_response = client.get_sports() print(f"\nAvailable sports: {len(sports_response['data'])}") print("Sample sports:") for sport in sports_response['data'][:5]: # Show first 5 sports print(f"- {sport.get('title', 'Unknown')}: {sport.get('key', 'Unknown')}") print(f"\nRemaining requests: {sports_response['headers']['x-requests-remaining']}") # Get a sport key for the next request sport_key = None for sport in sports_response['data']: if sport.get('title') == 'NBA': sport_key = sport.get('key') break if not sport_key: # If NBA is not found, use the first available sport sport_key = sports_response['data'][0].get('key') if sports_response['data'] else None if not sport_key: print("\nNo sports available to fetch odds") return # Get odds for the selected sport print(f"\nFetching odds for {sport_key}...") odds_options = { "regions": "us", "markets": "h2h", "oddsFormat": "decimal" } odds_response = client.get_odds(sport_key, odds_options) games_count = len(odds_response['data']) print(f"\nFetched odds for {games_count} games") if games_count > 0: print("\nSample games:") for game in odds_response['data'][:3]: # Show first 3 games home_team = game.get('home_team', 'Unknown') away_team = game.get('away_team', 'Unknown') commence_time = game.get('commence_time', 'Unknown') print(f"- {away_team} @ {home_team} (Start: {commence_time})") # Print sample odds from first bookmaker if available if game.get('bookmakers') and len(game.get('bookmakers')) > 0: bookmaker = game.get('bookmakers')[0] print(f" Odds from {bookmaker.get('title', 'Unknown')}:") for market in bookmaker.get('markets', []): if market.get('key') == 'h2h': print(" Moneyline:") for outcome in market.get('outcomes', []): print(f" {outcome.get('name', 'Unknown')}: {outcome.get('price', 'Unknown')}") print(f"\nRemaining requests: {odds_response['headers']['x-requests-remaining']}") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/capture_live_responses.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Script to capture live API responses from the Odds API. This script makes live API calls and saves the responses to JSON files in the mocks_live directory. These captures can be used for creating updated mock data for testing. IMPORTANT: Each live API call costs money. Use sparingly. """ import os import sys import json import asyncio from pathlib import Path from datetime import datetime # Add parent directory to path to import from wagyu_sports sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from wagyu_sports.odds_client import OddsClient async def capture_response(client, method_name, filename, **kwargs): """ Capture a response from the API and save it to a file. Args: client: The OddsClient instance method_name: The name of the method to call filename: The name of the file to save the response to **kwargs: Arguments to pass to the method """ print(f"Calling {method_name} with {kwargs}...") # Call the method method = getattr(client, method_name) result = method(**kwargs) # Add metadata response = { "_metadata": { "captured_at": datetime.now().isoformat(), "method": method_name, "parameters": kwargs }, "data": result } # Save to file output_path = Path(__file__).parent / "mocks_live" / filename with open(output_path, "w") as f: json.dump(response, f, indent=2) print(f"Response saved to {output_path}") return result async def main(): """Run the capture script.""" # Get API key from environment api_key = os.environ.get("ODDS_API_KEY") if not api_key: print("Error: ODDS_API_KEY environment variable is not set") sys.exit(1) print("=" * 80) print("Wagyu Sports Live API Response Capture") print("=" * 80) print() print("WARNING: This script makes live API calls that cost money.") print("Only run this script when necessary.") print() # Initialize client client = OddsClient(api_key) # Capture sports list await capture_response( client, "get_sports", "sports_list_live.json", all_sports=True ) # Capture NBA odds await capture_response( client, "get_odds", "nba_games_live.json", sport="basketball_nba", options={ "regions": "us", "markets": "h2h,spreads" } ) # Capture soccer odds await capture_response( client, "get_odds", "soccer_epl_live.json", sport="soccer_epl", options={ "regions": "us,uk", "markets": "h2h,spreads,totals" } ) # Print quota information print("\nQuota Information:") print(f"Remaining requests: {client.remaining_requests}") print(f"Used requests: {client.used_requests}") # Save quota information quota_info = { "_metadata": { "captured_at": datetime.now().isoformat() }, "remaining_requests": client.remaining_requests, "used_requests": client.used_requests } quota_path = Path(__file__).parent / "mocks_live" / "quota_info_live.json" with open(quota_path, "w") as f: json.dump(quota_info, f, indent=2) print(f"Quota information saved to {quota_path}") print("\nCapture complete!") if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: print("\nCapture stopped by user.") except Exception as e: print(f"\nError: {e}") ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/odds_client.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Wagyu Sports Client Module This module provides a client for interacting with sports betting data APIs. """ import requests from typing import Dict, List, Optional, Any, Union class OddsClient: """ Client for sports betting data. This class provides methods for fetching sports betting data including available sports and odds for specific sports. """ BASE_URL = "https://api.the-odds-api.com/v4" def __init__(self, api_key: str): """ Initialize the Wagyu Sports client. Args: api_key (str): API key for authentication with The Odds API """ self.api_key = api_key self.remaining_requests = None self.used_requests = None def get_sports(self, all_sports: bool = False) -> Dict[str, Any]: """ Get a list of available sports. Args: all_sports (bool, optional): Include out-of-season sports. Defaults to False. Returns: Dict[str, Any]: Response containing available sports data Raises: requests.exceptions.RequestException: If the request fails """ params = {"apiKey": self.api_key} if all_sports: params["all"] = "true" return self.make_request("/sports", params) def get_odds(self, sport: str, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ Get odds for a specific sport. Args: sport (str): Sport key (e.g., 'basketball_nba') options (Dict[str, Any], optional): Additional options for the request. Defaults to None. Possible options include: - regions: Comma-separated list of regions (e.g., 'us,uk') - markets: Comma-separated list of markets (e.g., 'h2h,spreads') - oddsFormat: Format for odds ('decimal' or 'american') - dateFormat: Format for dates ('unix' or 'iso') Returns: Dict[str, Any]: Response containing odds data Raises: requests.exceptions.RequestException: If the request fails """ endpoint = f"/sports/{sport}/odds" params = {"apiKey": self.api_key} if options: params.update(options) return self.make_request(endpoint, params) def make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ Make a request to the sports data API. Args: endpoint (str): API endpoint (e.g., '/sports') params (Dict[str, Any], optional): Query parameters. Defaults to None. Returns: Dict[str, Any]: Response data Raises: requests.exceptions.RequestException: If the request fails """ url = f"{self.BASE_URL}{endpoint}" response = requests.get(url, params=params) # Store quota information from headers if 'x-requests-remaining' in response.headers: self.remaining_requests = response.headers['x-requests-remaining'] if 'x-requests-used' in response.headers: self.used_requests = response.headers['x-requests-used'] # Raise exception for error status codes response.raise_for_status() # Return JSON response return { "data": response.json(), "headers": { "x-requests-remaining": self.remaining_requests, "x-requests-used": self.used_requests } } ``` -------------------------------------------------------------------------------- /wagyu_sports/odds_client.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Wagyu Sports Client Module This module provides a client for interacting with sports betting data APIs. """ import requests from typing import Dict, List, Optional, Any, Union class OddsClient: """ Client for sports betting data. This class provides methods for fetching sports betting data including available sports and odds for specific sports. """ BASE_URL = "https://api.the-odds-api.com/v4" def __init__(self, api_key: str): """ Initialize the Wagyu Sports client. Args: api_key (str): API key for authentication with The Odds API """ self.api_key = api_key self.remaining_requests = None self.used_requests = None def get_sports(self, all_sports: bool = False) -> Dict[str, Any]: """ Get a list of available sports. Args: all_sports (bool, optional): Include out-of-season sports. Defaults to False. Returns: Dict[str, Any]: Response containing available sports data Raises: requests.exceptions.RequestException: If the request fails """ params = {"apiKey": self.api_key} if all_sports: params["all"] = "true" return self.make_request("/sports", params) def get_odds(self, sport: str, options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ Get odds for a specific sport. Args: sport (str): Sport key (e.g., 'basketball_nba') options (Dict[str, Any], optional): Additional options for the request. Defaults to None. Possible options include: - regions: Comma-separated list of regions (e.g., 'us,uk') - markets: Comma-separated list of markets (e.g., 'h2h,spreads') - oddsFormat: Format for odds ('decimal' or 'american') - dateFormat: Format for dates ('unix' or 'iso') Returns: Dict[str, Any]: Response containing odds data Raises: requests.exceptions.RequestException: If the request fails """ endpoint = f"/sports/{sport}/odds" params = {"apiKey": self.api_key} if options: params.update(options) return self.make_request(endpoint, params) def make_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """ Make a request to the sports data API. Args: endpoint (str): API endpoint (e.g., '/sports') params (Dict[str, Any], optional): Query parameters. Defaults to None. Returns: Dict[str, Any]: Response data Raises: requests.exceptions.RequestException: If the request fails """ url = f"{self.BASE_URL}{endpoint}" response = requests.get(url, params=params) # Store quota information from headers if 'x-requests-remaining' in response.headers: self.remaining_requests = response.headers['x-requests-remaining'] if 'x-requests-used' in response.headers: self.used_requests = response.headers['x-requests-used'] # Raise exception for error status codes response.raise_for_status() # Return JSON response return { "data": response.json(), "headers": { "x-requests-remaining": self.remaining_requests, "x-requests-used": self.used_requests } } ``` -------------------------------------------------------------------------------- /old/espn_nonbetting_api/espn_api_endpoints.md: -------------------------------------------------------------------------------- ```markdown # ESPN's hidden API endpoints ## Football ### College Football **Latest News**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/news **Latest Scores**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard - query params: - calendar: 'blacklist' - dates: any date in YYYYMMDD **Game Information**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/summary?event=:gameId - params: - gameId: identifier of some game (EX: 400934572 for 2017 Army vs Navy) **Team Information**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/teams/:team - params: - team: some team abbreviation (EX: 'all' for Allegheny, 'gt' for Georgia Tech, 'wisconsin' for Wisconsin) **Rankings**: http://site.api.espn.com/apis/site/v2/sports/football/college-football/rankings ### NFL **Scores**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/football/nfl/teams/:team ## Baseball ### MLB **Scores**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/teams/:team ### College Baseball **Scores**: https://site.api.espn.com/apis/site/v2/sports/baseball/college-baseball/scoreboard ## Hockey **Scores**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/teams/:team ## Basketball ### NBA **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/nba/teams/:team ### WNBA **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/teams/:team ### Women's College Basketball **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/teams/:team ### Men's College Basketball **Scores**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard **News**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/news **All Teams**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/teams **Specific Team**: http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/teams/:team ## Soccer **Scores**: http://site.api.espn.com/apis/site/v2/sports/soccer/:league/scoreboard - params: - league: some league abbreviation (EX: 'eng.1' for EPL, 'usa.1' for MLS) **Latest News**: http://site.api.espn.com/apis/site/v2/sports/soccer/:league/news **List of Team Information**: http://site.api.espn.com/apis/site/v2/sports/soccer/:league/teams Will update with more information as I find more... ``` -------------------------------------------------------------------------------- /wagyu_sports/utils.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Utility functions for working with the Wagyu Sports client. """ import os import json import glob from typing import Dict, Any, Optional, Tuple from pathlib import Path from dotenv import load_dotenv from .odds_client import OddsClient def get_next_test_number() -> int: """ Get the next sequential test number for the output directory. This function looks at existing test directories and returns the next number in sequence. Returns: int: Next test number """ # Define the base directory for test outputs base_dir = os.path.join(os.getcwd(), "test_outputs") # Ensure the directory exists os.makedirs(base_dir, exist_ok=True) # Find all test directories test_dirs = glob.glob(os.path.join(base_dir, "test*")) if not test_dirs: return 1 # Extract numbers from directory names numbers = [] for dir_path in test_dirs: dir_name = os.path.basename(dir_path) try: # Extract number from "test{number}" num = int(dir_name.replace("test", "")) numbers.append(num) except ValueError: continue # Return next number in sequence return max(numbers) + 1 if numbers else 1 def save_response(filename: str, data: Dict[str, Any], test_number: Optional[int] = None) -> str: """ Save API response to a JSON file. Args: filename (str): Name of the file to save data (Dict[str, Any]): Data to save test_number (int, optional): Test number for directory. If None, uses next available. Returns: str: Path to the saved file """ # Get test number if not provided if test_number is None: test_number = get_next_test_number() # Define directory path dir_path = os.path.join(os.getcwd(), "test_outputs", f"test{test_number}") # Ensure directory exists os.makedirs(dir_path, exist_ok=True) # Define file path file_path = os.path.join(dir_path, filename) # Save data to file with open(file_path, 'w') as f: json.dump(data, f, indent=2) return file_path def test_wagyu_sports() -> Tuple[str, str]: """ Example function that demonstrates full API workflow. This function: 1. Loads the API key from environment variables 2. Creates an OddsClient instance 3. Fetches available sports 4. Fetches NBA odds 5. Saves responses to files Returns: Tuple[str, str]: Paths to the saved files (sports, odds) """ # Load environment variables load_dotenv(dotenv_path="config/.env") # Get API key api_key = os.getenv("ODDS_API_KEY") if not api_key: raise ValueError("ODDS_API_KEY not found in environment variables") # Create client client = OddsClient(api_key) # Get test number test_number = get_next_test_number() # Get available sports try: sports_response = client.get_sports() sports_file = save_response("1_available_sports.json", sports_response, test_number) print(f"Available sports saved to {sports_file}") print(f"Remaining requests: {sports_response['headers']['x-requests-remaining']}") except Exception as e: print(f"Error fetching sports: {e}") return "", "" # Get NBA odds try: odds_options = { "regions": "us", "markets": "h2h,spreads", "oddsFormat": "american" } odds_response = client.get_odds("basketball_nba", odds_options) odds_file = save_response("2_nba_odds.json", odds_response, test_number) print(f"NBA odds saved to {odds_file}") print(f"Remaining requests: {odds_response['headers']['x-requests-remaining']}") except Exception as e: print(f"Error fetching NBA odds: {e}") return sports_file, "" return sports_file, odds_file if __name__ == "__main__": # Run the test if this file is executed directly test_wagyu_sports() ``` -------------------------------------------------------------------------------- /wagyu_sports/tests/test_odds_api.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Tests for the Wagyu Sports client. This file contains all tests for the Wagyu Sports client, including: - Unit tests for the OddsClient class - Import verification - Installation verification """ import os import sys import pytest from unittest.mock import patch, MagicMock import importlib.util # Add the parent directory to the path so we can import the package sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Import the client from wagyu_sports import OddsClient from dotenv import load_dotenv def test_import(): """Test that the OddsClient can be imported.""" # If we got this far, the import worked assert OddsClient is not None # Try creating an instance client = OddsClient("test_key") assert client is not None assert client.api_key == "test_key" def test_dependencies(): """Test that required dependencies are installed.""" dependencies = ["requests", "dotenv"] for dep in dependencies: spec = importlib.util.find_spec(dep) assert spec is not None, f"{dep} is not installed" @pytest.fixture def client(): """Fixture to create an OddsClient instance.""" return OddsClient("test_api_key") @patch('requests.get') def test_get_sports(mock_get, client): """Test the get_sports method.""" # Mock response mock_response = MagicMock() mock_response.json.return_value = [ {"key": "sport1", "title": "Sport 1"}, {"key": "sport2", "title": "Sport 2"} ] mock_response.headers = { 'x-requests-remaining': '100', 'x-requests-used': '5' } mock_get.return_value = mock_response # Call the method result = client.get_sports() # Verify the request mock_get.assert_called_once_with( 'https://api.the-odds-api.com/v4/sports', params={'apiKey': 'test_api_key'} ) # Verify the result assert result['data'] == mock_response.json.return_value assert result['headers']['x-requests-remaining'] == '100' assert result['headers']['x-requests-used'] == '5' @patch('requests.get') def test_get_odds(mock_get, client): """Test the get_odds method.""" # Mock response mock_response = MagicMock() mock_response.json.return_value = [ { "id": "game1", "home_team": "Team A", "away_team": "Team B", "bookmakers": [] } ] mock_response.headers = { 'x-requests-remaining': '99', 'x-requests-used': '6' } mock_get.return_value = mock_response # Options for the request options = { "regions": "us", "markets": "h2h", "oddsFormat": "decimal" } # Call the method result = client.get_odds("basketball_nba", options) # Verify the request expected_params = {'apiKey': 'test_api_key'} expected_params.update(options) mock_get.assert_called_once_with( 'https://api.the-odds-api.com/v4/sports/basketball_nba/odds', params=expected_params ) # Verify the result assert result['data'] == mock_response.json.return_value assert result['headers']['x-requests-remaining'] == '99' assert result['headers']['x-requests-used'] == '6' @patch('requests.get') def test_make_request_error(mock_get, client): """Test error handling in make_request method.""" # Mock response with error mock_response = MagicMock() mock_response.raise_for_status.side_effect = Exception("API Error") mock_get.return_value = mock_response # Call the method and expect exception with pytest.raises(Exception): client.make_request("/test") # Verify the request was made mock_get.assert_called_once_with( 'https://api.the-odds-api.com/v4/test', params=None ) def test_api_key_env(): """Test that the API key can be loaded from environment variables.""" # Load environment variables from .env file load_dotenv(dotenv_path="config/.env") # Get API key api_key = os.getenv("ODDS_API_KEY") if not api_key: pytest.skip("ODDS_API_KEY not set in environment") # If we have an API key, create a client and verify it works # Note: This test only verifies the API key is loaded correctly, # it does NOT make any actual API calls client = OddsClient(api_key) assert client.api_key == api_key ``` -------------------------------------------------------------------------------- /wagyu_sports/tests/test_odds_mcp_server.py: -------------------------------------------------------------------------------- ```python """Tests for Wagyu Sports MCP server""" import os import sys import pytest import json # Add the parent directory to the path so we can import the package sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) from mcp.shared.memory import ( create_connected_server_and_client_session as client_session, ) from mcp.types import TextContent, TextResourceContents from wagyu_sports.mcp_server.odds_client_server import OddsMcpServer @pytest.mark.anyio async def test_get_sports(): """Test the get_sports tool""" server = OddsMcpServer(test_mode=True) async with client_session(server.server) as client: result = await client.call_tool("get_sports", {}) assert len(result.content) == 1 content = result.content[0] assert isinstance(content, TextContent) # Parse the JSON response response_data = json.loads(content.text) # Check that the response contains expected data assert "data" in response_data assert isinstance(response_data["data"], list) # Check for a specific sport (basketball_nba should be in the mock data) basketball_nba = next((s for s in response_data["data"] if s["key"] == "basketball_nba"), None) assert basketball_nba is not None assert basketball_nba["title"] == "NBA" @pytest.mark.anyio async def test_get_odds(): """Test the get_odds tool""" server = OddsMcpServer(test_mode=True) async with client_session(server.server) as client: result = await client.call_tool("get_odds", {"sport": "basketball_nba"}) assert len(result.content) == 1 content = result.content[0] assert isinstance(content, TextContent) # Parse the JSON response response_data = json.loads(content.text) # Check that the response contains expected data assert "data" in response_data assert isinstance(response_data["data"], list) # Check that at least one game is returned assert len(response_data["data"]) > 0 # Check that the first game has the expected fields first_game = response_data["data"][0] assert "sport_key" in first_game assert first_game["sport_key"] == "basketball_nba" assert "home_team" in first_game assert "away_team" in first_game assert "bookmakers" in first_game @pytest.mark.anyio async def test_get_odds_with_options(): """Test the get_odds tool with options""" server = OddsMcpServer(test_mode=True) async with client_session(server.server) as client: result = await client.call_tool( "get_odds", { "sport": "soccer_epl", "regions": "us", "markets": "h2h,spreads", "odds_format": "american", "date_format": "iso" } ) assert len(result.content) == 1 content = result.content[0] assert isinstance(content, TextContent) # Parse the JSON response response_data = json.loads(content.text) # Check that the response contains expected data assert "data" in response_data assert isinstance(response_data["data"], list) # Check that at least one game is returned assert len(response_data["data"]) > 0 # Check that the first game has the expected fields first_game = response_data["data"][0] assert "sport_key" in first_game assert first_game["sport_key"] == "soccer_epl" @pytest.mark.anyio async def test_get_quota_info(): """Test the get_quota_info tool""" server = OddsMcpServer(test_mode=True) async with client_session(server.server) as client: result = await client.call_tool("get_quota_info", {}) assert len(result.content) == 1 content = result.content[0] assert isinstance(content, TextContent) # Parse the JSON response response_data = json.loads(content.text) # Check that the response contains expected fields assert "remaining_requests" in response_data assert "used_requests" in response_data if __name__ == "__main__": pytest.main(["-xvs", __file__]) ``` -------------------------------------------------------------------------------- /wagyu_sports/examples/advanced_example.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Advanced example script demonstrating more complex usage of the Wagyu Sports client. This example shows: 1. Error handling 2. Handling API quota limits 3. Fetching multiple sports and odds 4. Filtering and processing data """ import os import time from datetime import datetime, timedelta from dotenv import load_dotenv import requests from wagyu_sports import OddsClient def format_datetime(dt_str): """Format ISO datetime string to a more readable format.""" try: dt = datetime.fromisoformat(dt_str.replace('Z', '+00:00')) return dt.strftime('%Y-%m-%d %H:%M:%S') except (ValueError, AttributeError): return dt_str def get_upcoming_games(client, sport_key, hours=24): """ Get upcoming games for a sport within the next X hours. Args: client (OddsClient): The Wagyu Sports client sport_key (str): Sport key (e.g., 'basketball_nba') hours (int): Number of hours to look ahead Returns: list: List of upcoming games """ try: # Get odds with options options = { "regions": "us", "markets": "h2h", "oddsFormat": "decimal", "dateFormat": "iso" } response = client.get_odds(sport_key, options) # Calculate cutoff time now = datetime.now() cutoff = now + timedelta(hours=hours) # Filter games by commence time upcoming_games = [] for game in response['data']: if 'commence_time' in game: try: game_time = datetime.fromisoformat( game['commence_time'].replace('Z', '+00:00') ) if now <= game_time <= cutoff: upcoming_games.append(game) except (ValueError, TypeError): # Skip games with invalid datetime continue return upcoming_games, response['headers']['x-requests-remaining'] except requests.exceptions.RequestException as e: print(f"Error fetching odds for {sport_key}: {e}") return [], None def find_best_odds(games): """ Find the best odds for each team across all bookmakers. Args: games (list): List of games with odds Returns: dict: Dictionary mapping team names to their best odds """ best_odds = {} for game in games: home_team = game.get('home_team') away_team = game.get('away_team') if not home_team or not away_team: continue # Initialize best odds for teams if not already present if home_team not in best_odds: best_odds[home_team] = 0 if away_team not in best_odds: best_odds[away_team] = 0 # Check all bookmakers for bookmaker in game.get('bookmakers', []): for market in bookmaker.get('markets', []): if market.get('key') == 'h2h': for outcome in market.get('outcomes', []): team = outcome.get('name') price = outcome.get('price') if team and price and team in best_odds: best_odds[team] = max(best_odds[team], price) return best_odds def main(): """Main function demonstrating advanced usage.""" # Load environment variables load_dotenv(dotenv_path="config/.env") # Get API key api_key = os.getenv("ODDS_API_KEY") if not api_key: print("Error: ODDS_API_KEY not found in environment variables") print("Please copy config/.env.example to config/.env and add your API key: ODDS_API_KEY=your_api_key_here") return # Create client client = OddsClient(api_key) try: # Get available sports print("Fetching available sports...") sports_response = client.get_sports() # Check if we have sports data if not sports_response['data']: print("No sports data available") return print(f"Found {len(sports_response['data'])} sports") print(f"Remaining requests: {sports_response['headers']['x-requests-remaining']}") # Get active sports (in-season) active_sports = [] for sport in sports_response['data']: if sport.get('active'): active_sports.append(sport) print(f"Found {len(active_sports)} active sports") # Process top 3 active sports for i, sport in enumerate(active_sports[:3]): sport_key = sport.get('key') sport_title = sport.get('title') print(f"\nProcessing {sport_title} ({sport_key})...") # Get upcoming games upcoming_games, remaining = get_upcoming_games(client, sport_key, hours=48) if not upcoming_games: print(f"No upcoming games found for {sport_title}") continue print(f"Found {len(upcoming_games)} upcoming games in the next 48 hours") # Find best odds best_odds = find_best_odds(upcoming_games) # Display results print(f"\nUpcoming {sport_title} games:") for game in upcoming_games[:5]: # Show up to 5 games home = game.get('home_team', 'Unknown') away = game.get('away_team', 'Unknown') time_str = format_datetime(game.get('commence_time', '')) print(f"- {away} @ {home} (Start: {time_str})") # Show bookmakers if game.get('bookmakers'): print(f" Available at {len(game['bookmakers'])} bookmakers") print(f"\nBest odds for {sport_title} teams:") sorted_odds = sorted(best_odds.items(), key=lambda x: x[1], reverse=True) for team, odds in sorted_odds[:5]: # Show top 5 teams by odds print(f"- {team}: {odds:.2f}") # Check remaining requests if remaining and int(remaining) < 10: print(f"\nWarning: Only {remaining} API requests remaining") print("Waiting 5 seconds before next request to avoid rate limiting...") time.sleep(5) print(f"Remaining requests: {remaining}") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/odds_client_server.py: -------------------------------------------------------------------------------- ```python #!/usr/bin/env python3 """ Wagyu Sports MCP Server Implementation This module provides an MCP server that exposes the Wagyu Sports API functionality through the Model Context Protocol. """ import os import sys import json import asyncio from typing import Dict, Any, Optional, List, Union from pathlib import Path from mcp.server.fastmcp import FastMCP from mcp.server.stdio import stdio_server import mcp.types as types try: # When imported as a package from .odds_client import OddsClient except ImportError: # When run directly from odds_client import OddsClient class OddsMcpServer: """MCP server for Wagyu Sports odds API.""" def __init__(self, api_key: Optional[str] = None, test_mode: bool = False): """ Initialize the MCP server. Args: api_key (str, optional): API key for the Odds API. If not provided, will try to get from environment variable. test_mode (bool): Whether to use mock data instead of real API calls. """ # Get API key from environment if not provided self.api_key = api_key or os.environ.get("ODDS_API_KEY") if not self.api_key and not test_mode: raise ValueError("API key is required when not in test mode") self.test_mode = test_mode self.mock_data_dir = Path(__file__).parent / "mocks_live" # Initialize client self.client = OddsClient(self.api_key) if not test_mode else None # Initialize server with FastMCP self.server = FastMCP("wagyu-sports-mcp") # Register tools self.register_tools() def register_tools(self): """Register MCP tools.""" @self.server.tool() async def get_sports(all_sports: bool = False, use_test_mode: Optional[bool] = None) -> str: """ Get a list of available sports. Args: all_sports: Include out-of-season sports if True use_test_mode: Override server test_mode setting (True for mock data, False for real API) Returns: JSON string with sports data """ # Determine if we should use test mode test_mode = use_test_mode if use_test_mode is not None else self.test_mode if test_mode: return await self._get_mock_data("sports_list_live.json") result = self.client.get_sports(all_sports=all_sports) return json.dumps(result, indent=2) @self.server.tool() async def get_odds(sport: str, regions: Optional[str] = None, markets: Optional[str] = None, odds_format: Optional[str] = None, date_format: Optional[str] = None, use_test_mode: Optional[bool] = None) -> str: """ Get odds for a specific sport. Args: sport: Sport key (e.g., 'basketball_nba') regions: Comma-separated list of regions (e.g., 'us,uk') markets: Comma-separated list of markets (e.g., 'h2h,spreads') odds_format: Format for odds ('decimal' or 'american') date_format: Format for dates ('unix' or 'iso') use_test_mode: Override server test_mode setting (True for mock data, False for real API) Returns: JSON string with odds data """ # Determine if we should use test mode test_mode = use_test_mode if use_test_mode is not None else self.test_mode if test_mode: if sport == "basketball_nba": return await self._get_mock_data("nba_games_live.json") # Fall back to nba_games_live.json since we don't have a live version of game_odds_all_books.json return await self._get_mock_data("nba_games_live.json") options = {} if regions: options["regions"] = regions if markets: options["markets"] = markets if odds_format: options["oddsFormat"] = odds_format if date_format: options["dateFormat"] = date_format result = self.client.get_odds(sport, options=options) return json.dumps(result, indent=2) @self.server.tool() async def get_quota_info(use_test_mode: Optional[bool] = None) -> str: """ Get API quota information. Args: use_test_mode: Override server test_mode setting (True for mock data, False for real API) Returns: JSON string with quota information """ # Determine if we should use test mode test_mode = use_test_mode if use_test_mode is not None else self.test_mode if test_mode: return await self._get_mock_data("quota_info_live.json") return json.dumps({ "remaining_requests": self.client.remaining_requests, "used_requests": self.client.used_requests }, indent=2) async def _get_mock_data(self, filename: str) -> str: """ Get mock data from a JSON file. Args: filename: Name of the mock data file Returns: JSON string with mock data """ try: mock_file = self.mock_data_dir / filename if not mock_file.exists(): return json.dumps({"error": f"Mock file {filename} not found"}) with open(mock_file, "r") as f: data = json.load(f) return json.dumps(data, indent=2) except Exception as e: return json.dumps({"error": f"Error loading mock data: {str(e)}"}) async def run(self): """Run the MCP server.""" # FastMCP has a different API for running the server # We need to use the run_stdio_async method directly await self.server.run_stdio_async() def main(): """Run the MCP server as a standalone process.""" # Parse arguments import argparse parser = argparse.ArgumentParser(description="Wagyu Sports MCP Server") parser.add_argument("--api-key", help="API key for the Odds API") parser.add_argument("--test-mode", action="store_true", help="Use mock data instead of real API calls") args = parser.parse_args() # Create and run server server = OddsMcpServer(api_key=args.api_key, test_mode=args.test_mode) asyncio.run(server.run()) if __name__ == "__main__": main() ``` -------------------------------------------------------------------------------- /old/docs/2025-01-10_odds_api_v4.md: -------------------------------------------------------------------------------- ```markdown # The Odds API v4 Documentation ## Overview The Odds API provides comprehensive access to sports betting data, including live odds, scores, and event information from multiple bookmakers worldwide. This document serves as a detailed reference for the API's capabilities and integration points. ## Host - Primary: `https://api.the-odds-api.com` - IPv6: `https://ipv6-api.the-odds-api.com` ## Authentication The Odds API uses API keys for authentication. All requests require an API key passed as a query parameter `apiKey`. ## Available Endpoints ### 1. Sports List (`GET /v4/sports`) **Cost:** Free (doesn't count against quota) **Capabilities:** - List all in-season sports - Option to include out-of-season sports - Provides sport keys used in other endpoints **Parameters:** - `apiKey`: Required - API authentication key - `all`: Optional - Include out-of-season sports if true ### 2. Odds Data (`GET /v4/sports/{sport}/odds`) **Cost:** 1 credit per region per market **Capabilities:** - Fetch odds for upcoming and live games - Multiple betting markets support - Regional bookmaker coverage - Customizable odds format - Optional bet limits information - Bookmaker deep linking **Markets Available:** - `h2h`: Head to head/moneyline - `spreads`: Points handicaps - `totals`: Over/under - `outrights`: Futures - `h2h_lay`: Lay odds (betting exchanges) - `outrights_lay`: Outright lay odds **Regions Available:** - `us`: United States - `us2`: United States (additional bookmakers) - `uk`: United Kingdom - `au`: Australia - `eu`: Europe **Parameters:** - `sport`: Required - Sport key from sports list - `regions`: Required - Comma-separated list of regions - `markets`: Optional - Comma-separated list of markets (default: h2h) - `dateFormat`: Optional - 'unix' or 'iso' (default: iso) - `oddsFormat`: Optional - 'decimal' or 'american' (default: decimal) - `eventIds`: Optional - Filter specific events - `bookmakers`: Optional - Filter specific bookmakers - `commenceTimeFrom`: Optional - Filter by start time (ISO 8601) - `commenceTimeTo`: Optional - Filter by end time (ISO 8601) - `includeLinks`: Optional - Include bookmaker links - `includeSids`: Optional - Include source IDs - `includeBetLimits`: Optional - Include betting limits ### 3. Scores (`GET /v4/sports/{sport}/scores`) **Cost:** - 1 credit for live and upcoming games - 2 credits when including historical data **Capabilities:** - Live scores (30-second updates) - Historical scores (up to 3 days) - Upcoming game schedules - Detailed scoring information - Match status tracking **Parameters:** - `sport`: Required - Sport key - `daysFrom`: Optional - Historical data (1-3 days) - `dateFormat`: Optional - 'unix' or 'iso' - `eventIds`: Optional - Filter specific events ### 4. Events (`GET /v4/sports/{sport}/events`) **Cost:** Free (doesn't count against quota) **Capabilities:** - List in-play and pre-match events - Basic game information - Team details - Event scheduling - Date range filtering **Parameters:** - `sport`: Required - Sport key - `dateFormat`: Optional - 'unix' or 'iso' - `eventIds`: Optional - Filter specific events - `commenceTimeFrom`: Optional - Start time filter (ISO 8601) - `commenceTimeTo`: Optional - End time filter (ISO 8601) ### 5. Event-Specific Odds (`GET /v4/sports/{sport}/events/{eventId}/odds`) **Cost:** Varies based on markets and regions **Capabilities:** - Detailed odds for single events - All available betting markets - Market-specific descriptions - Granular market updates - Same parameter options as main odds endpoint ### 6. Participants (`GET /v4/sports/{sport}/participants`) **Cost:** 1 credit **Capabilities:** - List all participants (teams or individuals) for a sport - Does not include players on teams - Includes both active and inactive participants **Parameters:** - `sport`: Required - Sport key - `apiKey`: Required - API authentication key ### 7. Historical Odds (`GET /v4/historical/sports/{sport}/odds`) **Cost:** 10 credits per region per market **Capabilities:** - Historical odds data from June 6th, 2020 - 10-minute intervals until September 2022 - 5-minute intervals after September 2022 - Available only on paid plans **Parameters:** - All parameters from the odds endpoint, plus: - `date`: Required - Timestamp for historical data (ISO 8601) ### 8. Historical Events (`GET /v4/historical/sports/{sport}/events`) **Cost:** 1 credit (free if no events found) **Capabilities:** - List historical events at a specified timestamp - Includes event details without odds - Useful for finding historical event IDs **Parameters:** - Same as events endpoint, plus: - `date`: Required - Timestamp for historical data (ISO 8601) ### 9. Historical Event Odds (`GET /v4/historical/sports/{sport}/events/{eventId}/odds`) **Cost:** 10 credits per region per market **Capabilities:** - Historical odds for a single event - Support for all betting markets - Additional markets available after May 3rd, 2023 - Available only on paid plans **Parameters:** - Same as event-specific odds endpoint, plus: - `date`: Required - Timestamp for historical data (ISO 8601) ## Integration Notes ### Quota Management - Track usage through response headers: - `x-requests-remaining`: Credits remaining until quota reset - `x-requests-used`: Credits used since last quota reset - `x-requests-last`: Usage cost of last API call - Costs vary by endpoint and parameters - Some endpoints are free - Multiple markets/regions multiply quota cost ### Best Practices 1. Use free endpoints for basic data 2. Batch requests when possible 3. Cache responses when appropriate 4. Monitor quota usage headers 5. Use event-specific endpoint for detailed market data 6. Filter by eventIds to reduce data volume ### Data Updates - Live scores update ~every 30 seconds - Odds updates vary by market and bookmaker - Events may become temporarily unavailable between rounds - Completed events are removed from odds endpoints - Historical scores available up to 3 days ### Special Features 1. **Betting Exchange Support** - Automatic lay odds inclusion - Bet limits information - Exchange-specific markets 2. **Deep Linking** - Bookmaker event links - Market-specific links - Betslip integration - Source IDs for custom linking 3. **Market Coverage** - Full coverage for major markets - Expanding coverage for additional markets - Sport-specific market availability - Regional variations in coverage ## Rate Limiting - Requests are rate limited to protect systems - Status code 429 indicates rate limit reached - Space out requests over several seconds when rate limited - Quota reset period defined by subscription - Usage tracked per endpoint - Some endpoints exempt from quota - Multiple markets/regions affect usage - Remaining quota in response headers ## Error Handling - API returns standard HTTP status codes - Error messages include descriptive text - Quota exceeded returns specific error - Invalid parameters clearly identified - Rate limiting information included ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/mocks_live/sports_list_live.json: -------------------------------------------------------------------------------- ```json { "_metadata": { "captured_at": "2025-03-03T01:43:40-08:00", "description": "Live data captured from the Odds API", "tool": "get_sports", "parameters": { "all_sports": true } }, "data": [ { "key": "americanfootball_cfl", "group": "American Football", "title": "CFL", "description": "Canadian Football League", "active": false, "has_outrights": false }, { "key": "americanfootball_ncaaf", "group": "American Football", "title": "NCAAF", "description": "US College Football", "active": true, "has_outrights": false }, { "key": "americanfootball_ncaaf_championship_winner", "group": "American Football", "title": "NCAAF Championship Winner", "description": "US College Football Championship Winner", "active": true, "has_outrights": true }, { "key": "americanfootball_nfl", "group": "American Football", "title": "NFL", "description": "US Football", "active": false, "has_outrights": false }, { "key": "americanfootball_nfl_preseason", "group": "American Football", "title": "NFL Preseason", "description": "US Football", "active": false, "has_outrights": false }, { "key": "americanfootball_nfl_super_bowl_winner", "group": "American Football", "title": "NFL Super Bowl Winner", "description": "Super Bowl Winner 2025/2026", "active": true, "has_outrights": true }, { "key": "americanfootball_ufl", "group": "American Football", "title": "UFL", "description": "United Football League", "active": false, "has_outrights": false }, { "key": "aussierules_afl", "group": "Aussie Rules", "title": "AFL", "description": "Aussie Football", "active": true, "has_outrights": false }, { "key": "baseball_kbo", "group": "Baseball", "title": "KBO", "description": "KBO League", "active": false, "has_outrights": false }, { "key": "baseball_milb", "group": "Baseball", "title": "MiLB", "description": "Minor League Baseball", "active": false, "has_outrights": false }, { "key": "baseball_mlb", "group": "Baseball", "title": "MLB", "description": "Major League Baseball", "active": true, "has_outrights": false }, { "key": "baseball_mlb_preseason", "group": "Baseball", "title": "MLB Preseason", "description": "Major League Baseball", "active": true, "has_outrights": false }, { "key": "baseball_mlb_world_series_winner", "group": "Baseball", "title": "MLB World Series Winner", "description": "World Series Winner 2025", "active": true, "has_outrights": true }, { "key": "baseball_ncaa", "group": "Baseball", "title": "NCAA Baseball", "description": "US College Baseball", "active": false, "has_outrights": false }, { "key": "baseball_npb", "group": "Baseball", "title": "NPB", "description": "Nippon Professional Baseball", "active": false, "has_outrights": false }, { "key": "basketball_euroleague", "group": "Basketball", "title": "Basketball Euroleague", "description": "Basketball Euroleague", "active": true, "has_outrights": false }, { "key": "basketball_nba", "group": "Basketball", "title": "NBA", "description": "US Basketball", "active": true, "has_outrights": false }, { "key": "basketball_nba_championship_winner", "group": "Basketball", "title": "NBA Championship Winner", "description": "Championship Winner 2024/2025", "active": true, "has_outrights": true }, { "key": "basketball_nba_preseason", "group": "Basketball", "title": "NBA Preseason", "description": "US Basketball", "active": false, "has_outrights": false }, { "key": "basketball_nbl", "group": "Basketball", "title": "NBL", "description": "AU National Basketball League", "active": true, "has_outrights": false }, { "key": "basketball_ncaab", "group": "Basketball", "title": "NCAAB", "description": "US College Basketball", "active": true, "has_outrights": false }, { "key": "basketball_ncaab_championship_winner", "group": "Basketball", "title": "NCAAB Championship Winner", "description": "US College Basketball Championship Winner", "active": true, "has_outrights": true }, { "key": "basketball_wnba", "group": "Basketball", "title": "WNBA", "description": "US Basketball", "active": false, "has_outrights": false }, { "key": "basketball_wncaab", "group": "Basketball", "title": "WNCAAB", "description": "US Women's College Basketball", "active": false, "has_outrights": false }, { "key": "boxing_boxing", "group": "Boxing", "title": "Boxing", "description": "Boxing Bouts", "active": true, "has_outrights": false }, { "key": "cricket_asia_cup", "group": "Cricket", "title": "Asia Cup", "description": "Asia Cup", "active": false, "has_outrights": false }, { "key": "cricket_big_bash", "group": "Cricket", "title": "Big Bash", "description": "Big Bash League", "active": false, "has_outrights": false }, { "key": "cricket_caribbean_premier_league", "group": "Cricket", "title": "CPLT20", "description": "Caribbean Premier League", "active": false, "has_outrights": false }, { "key": "cricket_icc_trophy", "group": "Cricket", "title": "ICC Champions Trophy", "description": "ICC Champions Trophy", "active": true, "has_outrights": false }, { "key": "cricket_icc_world_cup", "group": "Cricket", "title": "ICC World Cup", "description": "ICC World Cup", "active": false, "has_outrights": false }, { "key": "cricket_international_t20", "group": "Cricket", "title": "International Twenty20", "description": "International Twenty20", "active": false, "has_outrights": false }, { "key": "cricket_ipl", "group": "Cricket", "title": "IPL", "description": "Indian Premier League", "active": false, "has_outrights": false }, { "key": "cricket_odi", "group": "Cricket", "title": "One Day Internationals", "description": "One Day Internationals", "active": false, "has_outrights": false }, { "key": "cricket_psl", "group": "Cricket", "title": "Pakistan Super League", "description": "Pakistan Super League", "active": false, "has_outrights": false }, { "key": "cricket_t20_blast", "group": "Cricket", "title": "T20 Blast", "description": "T20 Blast", "active": false, "has_outrights": false }, { "key": "cricket_test_match", "group": "Cricket", "title": "Test Matches", "description": "International Test Matches", "active": false, "has_outrights": false }, { "key": "cricket_the_hundred", "group": "Cricket", "title": "The Hundred", "description": "The Hundred", "active": false, "has_outrights": false }, { "key": "golf_masters_tournament_winner", "group": "Golf", "title": "Masters Tournament Winner", "description": "2025 Winner", "active": true, "has_outrights": true }, { "key": "golf_pga_championship_winner", "group": "Golf", "title": "PGA Championship Winner", "description": "2025 Winner", "active": true, "has_outrights": true }, { "key": "golf_the_open_championship_winner", "group": "Golf", "title": "The Open Winner", "description": "2025 Winner", "active": true, "has_outrights": true }, { "key": "golf_us_open_winner", "group": "Golf", "title": "US Open Winner", "description": "2025 Winner", "active": true, "has_outrights": true }, { "key": "icehockey_ahl", "group": "Ice Hockey", "title": "AHL", "description": "American Hockey League", "active": false, "has_outrights": false }, { "key": "icehockey_liiga", "group": "Ice Hockey", "title": "Liiga", "description": "Finnish SM League", "active": true, "has_outrights": false }, { "key": "icehockey_mestis", "group": "Ice Hockey", "title": "Mestis", "description": "Finnish Mestis League", "active": true, "has_outrights": false }, { "key": "icehockey_nhl", "group": "Ice Hockey", "title": "NHL", "description": "US Ice Hockey", "active": true, "has_outrights": false }, { "key": "icehockey_nhl_championship_winner", "group": "Ice Hockey", "title": "NHL Championship Winner", "description": "Stanley Cup Winner 2024/2025", "active": true, "has_outrights": true }, { "key": "icehockey_sweden_allsvenskan", "group": "Ice Hockey", "title": "HockeyAllsvenskan", "description": "Swedish Hockey Allsvenskan", "active": true, "has_outrights": false }, { "key": "icehockey_sweden_hockey_league", "group": "Ice Hockey", "title": "SHL", "description": "Swedish Hockey League", "active": true, "has_outrights": false }, { "key": "lacrosse_ncaa", "group": "Lacrosse", "title": "NCAA Lacrosse", "description": "College Lacrosse", "active": true, "has_outrights": false }, { "key": "lacrosse_pll", "group": "Lacrosse", "title": "PLL", "description": "Premier Lacrosse League", "active": false, "has_outrights": false }, { "key": "mma_mixed_martial_arts", "group": "Mixed Martial Arts", "title": "MMA", "description": "Mixed Martial Arts", "active": true, "has_outrights": false }, { "key": "politics_us_presidential_election_winner", "group": "Politics", "title": "US Presidential Elections Winner", "description": "2028 US Presidential Election Winner", "active": false, "has_outrights": true }, { "key": "rugbyleague_nrl", "group": "Rugby League", "title": "NRL", "description": "Aussie Rugby League", "active": true, "has_outrights": false }, { "key": "rugbyunion_six_nations", "group": "Rugby Union", "title": "Six Nations", "description": "Six Nations Championship", "active": true, "has_outrights": false }, { "key": "soccer_africa_cup_of_nations", "group": "Soccer", "title": "Africa Cup of Nations", "description": "Africa Cup of Nations", "active": false, "has_outrights": false }, { "key": "soccer_argentina_primera_division", "group": "Soccer", "title": "Primera Divisi\u00f3n - Argentina", "description": "Argentine Primera Divisi\u00f3n", "active": true, "has_outrights": false }, { "key": "soccer_australia_aleague", "group": "Soccer", "title": "A-League", "description": "Aussie Soccer", "active": true, "has_outrights": false }, { "key": "soccer_austria_bundesliga", "group": "Soccer", "title": "Austrian Football Bundesliga", "description": "Austrian Soccer", "active": true, "has_outrights": false }, { "key": "soccer_belgium_first_div", "group": "Soccer", "title": "Belgium First Div", "description": "Belgian First Division A", "active": true, "has_outrights": false }, { "key": "soccer_brazil_campeonato", "group": "Soccer", "title": "Brazil S\u00e9rie A", "description": "Brasileir\u00e3o S\u00e9rie A", "active": true, "has_outrights": false }, { "key": "soccer_brazil_serie_b", "group": "Soccer", "title": "Brazil S\u00e9rie B", "description": "Campeonato Brasileiro S\u00e9rie B", "active": false, "has_outrights": false }, { "key": "soccer_chile_campeonato", "group": "Soccer", "title": "Primera Divisi\u00f3n - Chile", "description": "Campeonato Chileno", "active": true, "has_outrights": false }, { "key": "soccer_china_superleague", "group": "Soccer", "title": "Super League - China", "description": "Chinese Soccer", "active": false, "has_outrights": false }, { "key": "soccer_conmebol_copa_america", "group": "Soccer", "title": "Copa Am\u00e9rica", "description": "CONMEBOL Copa Am\u00e9rica", "active": false, "has_outrights": false }, { "key": "soccer_conmebol_copa_libertadores", "group": "Soccer", "title": "Copa Libertadores", "description": "CONMEBOL Copa Libertadores", "active": true, "has_outrights": false }, { "key": "soccer_denmark_superliga", "group": "Soccer", "title": "Denmark Superliga", "description": "Danish Soccer", "active": true, "has_outrights": false }, { "key": "soccer_efl_champ", "group": "Soccer", "title": "Championship", "description": "EFL Championship", "active": true, "has_outrights": false }, { "key": "soccer_england_efl_cup", "group": "Soccer", "title": "EFL Cup", "description": "League Cup", "active": true, "has_outrights": false }, { "key": "soccer_england_league1", "group": "Soccer", "title": "League 1", "description": "EFL League 1", "active": true, "has_outrights": false }, { "key": "soccer_england_league2", "group": "Soccer", "title": "League 2", "description": "EFL League 2 ", "active": true, "has_outrights": false }, { "key": "soccer_epl", "group": "Soccer", "title": "EPL", "description": "English Premier League", "active": true, "has_outrights": false }, { "key": "soccer_fa_cup", "group": "Soccer", "title": "FA Cup", "description": "Football Association Challenge Cup", "active": true, "has_outrights": false }, { "key": "soccer_fifa_world_cup", "group": "Soccer", "title": "FIFA World Cup", "description": "FIFA World Cup 2022", "active": false, "has_outrights": false }, { "key": "soccer_fifa_world_cup_winner", "group": "Soccer", "title": "FIFA World Cup Winner", "description": "FIFA World Cup Winner 2026", "active": true, "has_outrights": true }, { "key": "soccer_fifa_world_cup_womens", "group": "Soccer", "title": "FIFA Women's World Cup", "description": "FIFA Women's World Cup", "active": false, "has_outrights": false }, { "key": "soccer_finland_veikkausliiga", "group": "Soccer", "title": "Veikkausliiga - Finland", "description": "Finnish Soccer", "active": false, "has_outrights": false }, { "key": "soccer_france_ligue_one", "group": "Soccer", "title": "Ligue 1 - France", "description": "French Soccer", "active": true, "has_outrights": false }, { "key": "soccer_france_ligue_two", "group": "Soccer", "title": "Ligue 2 - France", "description": "French Soccer", "active": true, "has_outrights": false }, { "key": "soccer_germany_bundesliga", "group": "Soccer", "title": "Bundesliga - Germany", "description": "German Soccer", "active": true, "has_outrights": false }, { "key": "soccer_germany_bundesliga2", "group": "Soccer", "title": "Bundesliga 2 - Germany", "description": "German Soccer", "active": true, "has_outrights": false }, { "key": "soccer_germany_liga3", "group": "Soccer", "title": "3. Liga - Germany", "description": "German Soccer", "active": true, "has_outrights": false }, { "key": "soccer_greece_super_league", "group": "Soccer", "title": "Super League - Greece", "description": "Greek Soccer", "active": true, "has_outrights": false }, { "key": "soccer_italy_serie_a", "group": "Soccer", "title": "Serie A - Italy", "description": "Italian Soccer", "active": true, "has_outrights": false }, { "key": "soccer_italy_serie_b", "group": "Soccer", "title": "Serie B - Italy", "description": "Italian Soccer", "active": true, "has_outrights": false }, { "key": "soccer_japan_j_league", "group": "Soccer", "title": "J League", "description": "Japan Soccer League", "active": true, "has_outrights": false }, { "key": "soccer_korea_kleague1", "group": "Soccer", "title": "K League 1", "description": "Korean Soccer", "active": true, "has_outrights": false }, { "key": "soccer_league_of_ireland", "group": "Soccer", "title": "League of Ireland", "description": "Airtricity League Premier Division", "active": true, "has_outrights": false }, { "key": "soccer_mexico_ligamx", "group": "Soccer", "title": "Liga MX", "description": "Mexican Soccer", "active": true, "has_outrights": false }, { "key": "soccer_netherlands_eredivisie", "group": "Soccer", "title": "Dutch Eredivisie", "description": "Dutch Soccer", "active": true, "has_outrights": false }, { "key": "soccer_norway_eliteserien", "group": "Soccer", "title": "Eliteserien - Norway", "description": "Norwegian Soccer", "active": true, "has_outrights": false }, { "key": "soccer_poland_ekstraklasa", "group": "Soccer", "title": "Ekstraklasa - Poland", "description": "Polish Soccer", "active": true, "has_outrights": false }, { "key": "soccer_portugal_primeira_liga", "group": "Soccer", "title": "Primeira Liga - Portugal", "description": "Portugese Soccer", "active": true, "has_outrights": false }, { "key": "soccer_spain_la_liga", "group": "Soccer", "title": "La Liga - Spain", "description": "Spanish Soccer", "active": true, "has_outrights": false }, { "key": "soccer_spain_segunda_division", "group": "Soccer", "title": "La Liga 2 - Spain", "description": "Spanish Soccer", "active": true, "has_outrights": false }, { "key": "soccer_spl", "group": "Soccer", "title": "Premiership - Scotland", "description": "Scottish Premiership", "active": true, "has_outrights": false }, { "key": "soccer_sweden_allsvenskan", "group": "Soccer", "title": "Allsvenskan - Sweden", "description": "Swedish Soccer", "active": true, "has_outrights": false }, { "key": "soccer_sweden_superettan", "group": "Soccer", "title": "Superettan - Sweden", "description": "Swedish Soccer", "active": false, "has_outrights": false }, { "key": "soccer_switzerland_superleague", "group": "Soccer", "title": "Swiss Superleague", "description": "Swiss Soccer", "active": true, "has_outrights": false }, { "key": "soccer_turkey_super_league", "group": "Soccer", "title": "Turkey Super League", "description": "Turkish Soccer", "active": true, "has_outrights": false }, { "key": "soccer_uefa_champs_league", "group": "Soccer", "title": "UEFA Champions League", "description": "European Champions League", "active": true, "has_outrights": false }, { "key": "soccer_uefa_champs_league_qualification", "group": "Soccer", "title": "UEFA Champions League Qualification", "description": "European Champions League Qualification", "active": false, "has_outrights": false }, { "key": "soccer_uefa_euro_qualification", "group": "Soccer", "title": "UEFA Euro Qualification", "description": "European Championship Qualification", "active": false, "has_outrights": false }, { "key": "soccer_uefa_europa_conference_league", "group": "Soccer", "title": "UEFA Europa Conference League", "description": "UEFA Europa Conference League", "active": true, "has_outrights": false }, { "key": "soccer_uefa_europa_league", "group": "Soccer", "title": "UEFA Europa League", "description": "European Europa League", "active": true, "has_outrights": false }, { "key": "soccer_uefa_european_championship", "group": "Soccer", "title": "UEFA Euro 2024", "description": "UEFA European Championship", "active": false, "has_outrights": false }, { "key": "soccer_uefa_nations_league", "group": "Soccer", "title": "UEFA Nations League", "description": "UEFA Nations League", "active": true, "has_outrights": false }, { "key": "soccer_usa_mls", "group": "Soccer", "title": "MLS", "description": "Major League Soccer", "active": true, "has_outrights": false }, { "key": "tennis_atp_aus_open_singles", "group": "Tennis", "title": "ATP Australian Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_canadian_open", "group": "Tennis", "title": "ATP Canadian Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_china_open", "group": "Tennis", "title": "ATP China Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_cincinnati_open", "group": "Tennis", "title": "ATP Cincinnati Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_dubai", "group": "Tennis", "title": "ATP Dubai", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_french_open", "group": "Tennis", "title": "ATP French Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_paris_masters", "group": "Tennis", "title": "ATP Paris Masters", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_qatar_open", "group": "Tennis", "title": "ATP Qatar Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_shanghai_masters", "group": "Tennis", "title": "ATP Shanghai Masters", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_us_open", "group": "Tennis", "title": "ATP US Open", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_atp_wimbledon", "group": "Tennis", "title": "ATP Wimbledon", "description": "Men's Singles", "active": false, "has_outrights": false }, { "key": "tennis_wta_aus_open_singles", "group": "Tennis", "title": "WTA Australian Open", "description": "Women's Singles", "active": false, "has_outrights": false } ], "headers": { "x-requests-remaining": "492", "x-requests-used": "8" } } ``` -------------------------------------------------------------------------------- /wagyu_sports/mcp_server/mocks_live/nba_games_live.json: -------------------------------------------------------------------------------- ```json { "_metadata": { "captured_at": "2025-03-03T01:47:34-08:00", "description": "Live data captured from the Odds API", "tool": "get_odds", "parameters": { "sport": "basketball_nba", "regions": "us", "markets": "h2h,spreads" } }, "data": [ { "id": "88ec7352b864871886a86b3bcb559179", "sport_key": "basketball_nba", "sport_title": "NBA", "commence_time": "2025-03-04T00:10:00Z", "home_team": "Charlotte Hornets", "away_team": "Golden State Warriors", "bookmakers": [ { "key": "draftkings", "title": "DraftKings", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 5.55 }, { "name": "Golden State Warriors", "price": 1.16 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.89, "point": 12.0 }, { "name": "Golden State Warriors", "price": 1.93, "point": -12.0 } ] } ] }, { "key": "fanduel", "title": "FanDuel", "last_update": "2025-03-03T09:43:06Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 6.3 }, { "name": "Golden State Warriors", "price": 1.13 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.91, "point": 12.5 }, { "name": "Golden State Warriors", "price": 1.91, "point": -12.5 } ] } ] }, { "key": "betrivers", "title": "BetRivers", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 5.5 }, { "name": "Golden State Warriors", "price": 1.16 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.88, "point": 12.0 }, { "name": "Golden State Warriors", "price": 1.93, "point": -12.0 } ] } ] }, { "key": "betmgm", "title": "BetMGM", "last_update": "2025-03-03T09:44:27Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 5.5 }, { "name": "Golden State Warriors", "price": 1.16 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.87, "point": 12.5 }, { "name": "Golden State Warriors", "price": 1.95, "point": -12.5 } ] } ] }, { "key": "betonlineag", "title": "BetOnline.ag", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 5.6 }, { "name": "Golden State Warriors", "price": 1.17 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.95, "point": 12.0 }, { "name": "Golden State Warriors", "price": 1.87, "point": -12.0 } ] } ] }, { "key": "lowvig", "title": "LowVig.ag", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 5.6 }, { "name": "Golden State Warriors", "price": 1.17 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.99, "point": 12.0 }, { "name": "Golden State Warriors", "price": 1.9, "point": -12.0 } ] } ] }, { "key": "bovada", "title": "Bovada", "last_update": "2025-03-03T09:45:54Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:54Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 5.6 }, { "name": "Golden State Warriors", "price": 1.15 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:54Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.95, "point": 11.5 }, { "name": "Golden State Warriors", "price": 1.87, "point": -11.5 } ] } ] }, { "key": "betus", "title": "BetUS", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Charlotte Hornets", "price": 1.95, "point": 11.5 }, { "name": "Golden State Warriors", "price": 1.87, "point": -11.5 } ] } ] } ] }, { "id": "d9ffe1f98222bd3881003a3eb3bf3178", "sport_key": "basketball_nba", "sport_title": "NBA", "commence_time": "2025-03-04T00:10:00Z", "home_team": "Philadelphia 76ers", "away_team": "Portland Trail Blazers", "bookmakers": [ { "key": "betonlineag", "title": "BetOnline.ag", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.62 }, { "name": "Portland Trail Blazers", "price": 2.42 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.91, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.91, "point": 3.5 } ] } ] }, { "key": "lowvig", "title": "LowVig.ag", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.62 }, { "name": "Portland Trail Blazers", "price": 2.42 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.94, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.94, "point": 3.5 } ] } ] }, { "key": "draftkings", "title": "DraftKings", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.62 }, { "name": "Portland Trail Blazers", "price": 2.36 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.89, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.93, "point": 3.5 } ] } ] }, { "key": "fanduel", "title": "FanDuel", "last_update": "2025-03-03T09:43:06Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.62 }, { "name": "Portland Trail Blazers", "price": 2.36 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.91, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.91, "point": 3.5 } ] } ] }, { "key": "betrivers", "title": "BetRivers", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.66 }, { "name": "Portland Trail Blazers", "price": 2.28 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.93, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.88, "point": 3.5 } ] } ] }, { "key": "betmgm", "title": "BetMGM", "last_update": "2025-03-03T09:44:27Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.61 }, { "name": "Portland Trail Blazers", "price": 2.35 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.91, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.91, "point": 3.5 } ] } ] }, { "key": "bovada", "title": "Bovada", "last_update": "2025-03-03T09:45:54Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:54Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.61 }, { "name": "Portland Trail Blazers", "price": 2.4 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:54Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.91, "point": -4.0 }, { "name": "Portland Trail Blazers", "price": 1.91, "point": 4.0 } ] } ] }, { "key": "betus", "title": "BetUS", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Philadelphia 76ers", "price": 1.87, "point": -3.5 }, { "name": "Portland Trail Blazers", "price": 1.95, "point": 3.5 } ] } ] } ] }, { "id": "01703ec69023c48dec6d3a5cef234cfb", "sport_key": "basketball_nba", "sport_title": "NBA", "commence_time": "2025-03-04T00:40:00Z", "home_team": "Miami Heat", "away_team": "Washington Wizards", "bookmakers": [ { "key": "draftkings", "title": "DraftKings", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Miami Heat", "price": 1.25 }, { "name": "Washington Wizards", "price": 4.1 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Miami Heat", "price": 1.89, "point": -8.5 }, { "name": "Washington Wizards", "price": 1.93, "point": 8.5 } ] } ] }, { "key": "fanduel", "title": "FanDuel", "last_update": "2025-03-03T09:43:06Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Miami Heat", "price": 1.26 }, { "name": "Washington Wizards", "price": 4.1 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Miami Heat", "price": 1.91, "point": -9.0 }, { "name": "Washington Wizards", "price": 1.91, "point": 9.0 } ] } ] }, { "key": "betrivers", "title": "BetRivers", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Miami Heat", "price": 1.27 }, { "name": "Washington Wizards", "price": 3.95 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Miami Heat", "price": 1.93, "point": -9.0 }, { "name": "Washington Wizards", "price": 1.88, "point": 9.0 } ] } ] }, { "key": "betmgm", "title": "BetMGM", "last_update": "2025-03-03T09:44:27Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Miami Heat", "price": 1.25 }, { "name": "Washington Wizards", "price": 4.1 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Miami Heat", "price": 1.91, "point": -8.5 }, { "name": "Washington Wizards", "price": 1.91, "point": 8.5 } ] } ] }, { "key": "betonlineag", "title": "BetOnline.ag", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Miami Heat", "price": 1.26 }, { "name": "Washington Wizards", "price": 4.1 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Miami Heat", "price": 1.95, "point": -9.0 }, { "name": "Washington Wizards", "price": 1.87, "point": 9.0 } ] } ] }, { "key": "lowvig", "title": "LowVig.ag", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Miami Heat", "price": 1.26 }, { "name": "Washington Wizards", "price": 4.1 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Miami Heat", "price": 1.99, "point": -9.0 }, { "name": "Washington Wizards", "price": 1.9, "point": 9.0 } ] } ] }, { "key": "bovada", "title": "Bovada", "last_update": "2025-03-03T09:45:54Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:54Z", "outcomes": [ { "name": "Miami Heat", "price": 1.24 }, { "name": "Washington Wizards", "price": 4.15 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:54Z", "outcomes": [ { "name": "Miami Heat", "price": 1.95, "point": -9.0 }, { "name": "Washington Wizards", "price": 1.87, "point": 9.0 } ] } ] }, { "key": "betus", "title": "BetUS", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Miami Heat", "price": 1.95, "point": -9.0 }, { "name": "Washington Wizards", "price": 1.87, "point": 9.0 } ] } ] } ] }, { "id": "7d360fe51d388cd45d67ca0791a15e4d", "sport_key": "basketball_nba", "sport_title": "NBA", "commence_time": "2025-03-04T01:10:00Z", "home_team": "Memphis Grizzlies", "away_team": "Atlanta Hawks", "bookmakers": [ { "key": "draftkings", "title": "DraftKings", "last_update": "2025-03-03T09:45:51Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 3.85 }, { "name": "Memphis Grizzlies", "price": 1.28 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:51Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 1.93, "point": 8.5 }, { "name": "Memphis Grizzlies", "price": 1.89, "point": -8.5 } ] } ] }, { "key": "fanduel", "title": "FanDuel", "last_update": "2025-03-03T09:43:06Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 3.65 }, { "name": "Memphis Grizzlies", "price": 1.3 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:43:06Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 1.93, "point": 8.5 }, { "name": "Memphis Grizzlies", "price": 1.89, "point": -8.5 } ] } ] }, { "key": "betrivers", "title": "BetRivers", "last_update": "2025-03-03T09:45:52Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 3.7 }, { "name": "Memphis Grizzlies", "price": 1.29 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:45:52Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 1.93, "point": 8.5 }, { "name": "Memphis Grizzlies", "price": 1.88, "point": -8.5 } ] } ] }, { "key": "betmgm", "title": "BetMGM", "last_update": "2025-03-03T09:44:27Z", "markets": [ { "key": "h2h", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 3.9 }, { "name": "Memphis Grizzlies", "price": 1.27 } ] }, { "key": "spreads", "last_update": "2025-03-03T09:44:27Z", "outcomes": [ { "name": "Atlanta Hawks", "price": 1.91, "point": 8.5 }, { "name": "Memphis Grizzlies", "price": 1.91, "point": -8.5 } ] } ] } ] } ], "headers": { "x-requests-remaining": "488", "x-requests-used": "12" } } ``` -------------------------------------------------------------------------------- /docs/reagan_planning/meeting with reagan/transcript.md: -------------------------------------------------------------------------------- ```markdown You OK, all right so Quick story Harrison and I are competing in an AI hacker on this weekend Big money prize be to California and we're just like on Adderall building shit all right so the idea we came up with last night is related to sports gambling of course and It's an example for what your pitching stuff Give him the tiniest bit of background and then ask him why he's here so Brandon's gonna explain but I just got this advice from a person at work so sports betting and AI having up-to-date odds right when you ask yeah So get out of your mind that we're trying to break sports because I know that's your thing we're not we're not trying to break sports. We just we have access to all of the odds at all of the sports betting websites right now. Reagan Shu Yeah You And so any any line any Reagan Shu Powerful You country any sports betting website we have we have all that data in. We don't have enough time to do it good enough yet for everything so our idea is major sports in the US right now so we're going. It's kind of a hard time. I know there's no sports if we can get hockey we'll see, but the idea is football basketball baseball yeah but but you know like it whatever to keep it small. So what Reagan Shu What? You imagine you know whether it's just asking for the odds or something but like we're trying to build out some cause like we have the decoding idea but we're trying to figure out like the most important thing is the demo and say like here's why someone would care you know and you're the person that would care so back to Brandon yeah So yeah, basically right now imagine working with ChatGPT and saying what is the line for this game Reagan Shu Yeah You and yeah I mean or if you wanted or way more you could get you could get teams you could get what games are happening today you can get with the lines are for those specific lines You could arbitrage basically and say you know what are the lines for each of the different gambling sites So yeah, I mean I know it's based functionality. You're not like making money off of this, but like if if it was just in front of your lap, your ChatGPT that had access to the data. But this does this hackathon what we're doing and the whole purpose of like this is it's not new analytics like Brandon is saying it's removal of friction yeah that's just that's all it is so from Reagan Shu Yeah You there we need ask us questions for we move to the next piece Reagan Shu Yeah, I'm at work but my first thoughts are like if you are assuming that you're you know like an avid gambler like having access to all of the different site a different lines of different ones cause they are gonna be different because each book is gonna be getting like a different sort of weight of like bats and money so like You Yeah Reagan Shu the line will always be a little different I guess like if you know if you're if you're shopping basically like you can kinda turn it almost into like a shopping like marketplace like almost like thinking you know you can like OK if I want to take like you know You Oh Reagan Shu Kansas college basketball tonight right and I know that like you know some books will have them at four some at 4 1/2 some of them like the odds it'll be minimal but to a real better like those matter, you know also when it comes You Yeah Reagan Shu to like inability to hedge having that little edge like does is convenient, especially when it comes to like futures are the big one like if you wanna bet on who's gonna be the You Keep giving examples Reagan Shu NFL like if you wanna be if you wanna be on who's gonna be the NFL MVP right? Most places to have like Lamar Jackson as a top one right but the actual number of the favorite like the actual odds are gonna be different based on how many people have bet through that book so like if you're looking to place a bet like that for futures, you wanna basically get the highest odds right even though it's the same debt so like You Yeah, so so features is the most value because it's their long return bets give more time for the sports books to be there to be variation between the sports books more Reagan Shu Yeah, yeah You volatile OK, so can we like the ideas when we get back? I get back to Atlanta on Stuffle keep going for now because basically we have today. We have tomorrow tiny bit but we have today and we're just gonna grind. We're on Addie. We're doing all things we also have a fire going yeah we're cleaning Brandon smoker by smoking it really hard and Reagan Shu Nice You upset so Can you like the reason why we called you is cause we love you and Reagan Shu Thank you I love you too. I'm happy you called. You The other reason because we do love a lot of people is you know about sports but not like you though but more so what we need right now is like based off of what we can build right now and what we described we need like user stories or like actual like specific experiments or whatever you wanna call it so for example and I want you to use as reference, but like give the actual example Like say it was an AI thing instead of for sports betting is like Brandon doing his like project or support stuff like the other story could be. I have ticket number 002 and I want to know what was the message that the person sent regarding this ticket so I can quickly find out OK cool here's the message bang. Were there any similar messages to this OK bang so now if we go back to sports betting like if you take yourself say March madness you're putting together all your bets for March madness and you know you're doing that stuff give us some things you wanna ask or find out related to these odds are like even if there's a piece it's like a builder but like give us the user story your stories of like oh that'll be cool. Reagan Shu Yeah, I think so. Thinking of like March madness coming up right it's like underdog Central so I would say I want I You Yeah, pick pick bullshit names without knowing anything to like feel like Reagan Shu want. You oil Reagan Shu Yeah, I would say like yeah I would. I would say something like I wanna pick the 12 seat to upset the five seed in You Marymont around 16 or whatever Reagan Shu every every division, right You Hello Reagan Shu Find me the best find me the highest odds for each of those 12 like test and then like I'm thinking I could say OK so if Nevada is offering you know loyal on their amount at like +9 00 but somebody else like DraftKings housing +925 right like obviously since you already had your mind made of who you wanna pick I'm thinking that would be like a way to basically direct somebody to where they can get the best odds You OK, so we pivot we get more towards where are the on sale? Reagan Shu Because like in theory right like a few or if you wanted to do that, I could open up each tab like if me and Brandon had a separate book I could say like I've done this before I was like hey I have a You Yeah, yeah Reagan Shu Stafford ass Super Bowl MVP at like +4 00 what do you have of that and he says oh I've got a +515 for like really peculiar particular bets like that they will actually be quite different You Yeah, OK Reagan Shu so I think like it it's kind of like it could You OK Reagan Shu eliminate it, centralized the different places you would pull from if you wanna do that That's by that's how I like if you gave it to me that's how I would first use it again knowing what I wanna bet so thinking of it as like something that helps you make a bet or help you choose your bet but helps you like literally confirm the choice that you've already kind of made coming into it You Look up thought yeah no thoughts I think that's so. It's number one number two. Pretend you are either showing someone who's a semi nova sports better in like the hard-core sense you know they're putting together a bit or whatever but like they're not trying to just do some bullshit And say we got a Lakers game tonight that's now now run Reagan Shu I would say So you're saying like you're casual better and you're like we're all getting to get a watch the Lakers game and you're like interested in something on it I would say You Yeah, exactly exactly in my sports be friends and I'm like I wanna put a bed down six dudes on the couch and only one dude actually Reagan Shu Yeah, I would say You bats and the others talk about it high-level Reagan Shu like identifying identifying trends so like if somebody's like, but they like Lucas over his shit like it's like points rebounds and assists over his hit like the past three games or he's gone like 5 00 like something like that so maybe help you identify a trend that you can either like jump on or fade depending on what you wanna do but I guess like it'd be something to give you You I want that and there might be part can I can I can? I slightly Reagan Shu more You reposition the idea the thing won't give you any old Reagan Shu Yeah Yeah You information. It will only give you future information or current information. Reagan Shu Oh, OK. Up-to-date current information. You We could add that though in the future Reagan Shu Couldn't give you something like you know the Lakers or you know 40 12 and You Yes Reagan Shu 13 against the spread like for like as far as the seasons gone You Potentially there's a lot of I got a look potentially. I'm thinking even even lower of like I don't even know if this is real and that's why we're telling you, but I would be like OK. I like the Reagan Shu Yeah Yeah, then I would actually, I would actually go this way if you wanna go yeah You Lakers what if I like wanted to like a cake or watching the Lakers game yeah Reagan I know you got some crazy shit down while that's like. I don't even know what you said. I wanna throw one down who's your favorite player Luca just moved over that's cool. Let's make a bet with Luca. Reagan Shu Yeah, yeah, I would also say You Like how how do you how do you progress this to the point of a bit being placed yeah Reagan Shu Yeah, like if you just provide like Lucas odds, you know like it cause that you can one there's multiple layers obviously like a 501 right like it's 23, 000 over 42 over under 42 which is like pretty standard for a guy like that, but you can do like over 60 and that's gonna be crazy odds or you can do like over under 20 and it's gonna be like safer odds so I think one you just have to keep in mind the options which most books will provide because They will it's part of it. I would also say just You Even a little further back, I'm sorry to cut you off Reagan Shu following like if it's. You but with only the day Like The flow would be because we can build on if we get data but we have to like basically at this point say if we can just sort whatever we end up getting versus ask for something specific Reagan Shu Yeah You So like you know if we get all of these normal odds of the spread money line whatever and then is there anything or spend money on the same no spread money line over under like basic basic basic basic basic basic Reagan Shu Yeah Let me rewind for a second then I would say that another big thing that people want to look for, but it's hard to do is line movement so if there's a lot of action on specific debt, the line will move right like that like the books react by moving the line or moving the You It's really OK Reagan Shu odds and people look for people look for that yeah You OK contextualize give me an example Reagan Shu so I'll give you the best example that I can think of like two years ago Philadelphia and the Cowboys played and like came out at the starting quarterback for Philadelphia isn't gonna play so the line You I got Reagan Shu changes right? That's that's a pretty dressing. Example line changes cause they're going into it with a back up, so Philadelphia went from being like -3 favorites 2+ one underdog so if you had right like knowing that change, but I even think just like. You Story API Reagan Shu And less in less Jurassic context if there's like any baseball example on any given random day out of 162 gauge like July 8 in the middle of the fucking summer when nobody's watching a paying attention for whatever reason a lot of the people bet on the You I love it Reagan Shu braves to beat the White Sox right the White Sox are ass right that line move that has more people bet on the Braves line is gonna continue to move so if you think that you special at the end of the day, I should know what housing gonna happen on July 8 on a random day You can basically monitor the White Sox and be like the White Sox are randomly like +375 for this random day where it's both their fifth starters and nobody knows you playing games where you think you might basically just be like an advantage that's notable movement because people like movement and like movement draws attention because it kind of makes people Intrinsically wonder why is this moving like what is everybody else think about the Braves on this random day why not take the white Sox right Brandon do you remember that random game Houston versus Detroit Detroit was +400 and ended up winning right like it just a random day in the middle of summer so I think I like You Yeah Reagan Shu baseball and basketball special like college basketball right those like random games will get a random amount of attention and if that moves whether it's for one way or another, it doesn't matter people like to know about movement You So movement, you think is the biggest variable for people like like you said no one beats Vegas but if anyone's gonna try to beat Vegas, they they use movement as a massive variable for that movement of lines Reagan Shu Yes, absolutely absolutely movement and waiting of waiting of the number of fats versus the money so like the amount of money on the game on one side of a bat is different than the amount of bets on one You Would you Reagan Shu side of the game You be a thing you can find a thing you've ever seen where it's like Reagan Shu Yeah, so You there's this much money on one side of the bed Reagan Shu some books will provide it like some of the new ones like FanDuel will do it like FanDuel DraftKings. They'll do that kind of stuff and again that's gonna be different for each book but yes, they will show you like what You Yeah Reagan Shu percent of the money like what percent of the money is on which side versus what percent of the bets are on which side but they kinda break it down so you might be able to pull that You Do you think it would do you think it would clutter a Response from a GPT if we showed that data along with the lines or you think that's like naturally, someone's gonna want that along with like if you're just like it with the line for the Lakers tonight and then you can say you can also you can show each Reagan Shu Yeah You sports book and then you can also show like how much money is on each side and then maybe some like movement variable or something Reagan Shu Yeah, but I mean people like oh it's literally a drop down underneath the lawn and some of these so like if you're if you click into the game and you see the You Yeah Reagan Shu line the scale will pop up then it'll be like it'll look like almost an election chart like it'll be blue on one side red on the other and it'll say like 46 and 54 You Use Reagan Shu % like 46 % of the money is on You know like the Lakers, but 54% of the bats are on whatever right like the opposite of it You Yeah, so very important here is what your ear you're saying implicitly cause you have the information that's what I want so that you're seeing these things and you're saying the number of bets in like the value of the bets is important If you look at a snapshot in time right now you weren't tracking it just look right now. How like is it thresholds or is it like kind of a ratio of bets place to amount of money like what give us how you would analyze those two variables to help you OK Reagan Shu It's Russia it's ratio it's ratio of basically the money like the total action on that game You OK, so insert that that is this the Detroit game you're talking about Steelers is different now Reagan Shu I can go for either. I get this interchangeable. We can make it to Detroit example if you want. You Cell Give me both I'd like just like verbalize it Reagan Shu Yeah, so like let's say you wanna bet on this Detroit versus Houston game Detroit is +400 You It's Tuesday at Reagan Shu underdog yeah random day You 12 it's Tuesday at 12 space Reagan Shu exactly and so You Yes, random game Reagan Shu you basically you click on it and the options come up right you have the run line you have to over under and then you have the money line and then below that is going to be I got a picture and election bar that has blue on the left and red on the right and it'll be equal 100 % but it'll basically show you what percent of the money is on which side and what percent of the best on which side? So in theory, why if you say OK I'm not like 80% of the best are on like that place like number of best place 80% of them of the total best place on that game through that site or on Houston basically clean up this game right but so in theory right like 20% of the best are on Try to beat them but 40% of the money actually place like a You Home Reagan Shu total the total action on that game numerical like currency wise 40% of the actual money bet is on Detroit And 60% of the total like pool is fat is that on You Yeah, you're like oh interesting Reagan Shu Houston so people would if people like to fade the public right like that's kind of a thing that better try to do the other one ride with the public or better or go against it cause you know think about it like if you're literally in your own head like Vegas know something that nobody else does right You Public against public Reagan Shu everybody's on you know Houston to win this game in Vegas is letting it happen and right like there must be like stupid shit like that, but the people do like to know what the number like where the weight is of the You Yeah, yeah, can you give me like? Reagan Shu public You Role-play that person Who is doing that with that exact line walk me through the mental numbers Matthew do to say OK I see X on one side of number of bets. Why on the actual dollars placed on the bets let me like. Tell me your thought process that would lead you to I'm gonna bet on and the name X or Y or whatever side. Reagan Shu Yeah, so I see that there's I see that there's a random game where you can get White Sox +400 which is quite absurd for again again where anything can happen in the middle of the summer so You Yeah Reagan Shu naturally that's what like sparks my interest in clicking it and then assuming you strolling out the rest of the bets for that day and then once I'm in there, right, I see that 40% of the money is on Is on Detroit, which means that the email there's less that's on it. The people who are betting on Detroit are betting bigger on it right You And that makes you feel Reagan Shu like You how and why Reagan Shu That makes me feel that there's a small percentage of people that really believe in this day that believe Troy is going to you know You Call mom Reagan Shu that and that is also just like the idea of the odds are not in your favor, but the odds are great like for considering what the game is again being a middle of the road game that nobody cares about the payout on that if you really think it's just a 50-50 toss up like it's a no-brainer to take the one where you could quadruple your money right versus you know taking the safer bet that is again the team that is way better but again it's a baseball anything can happen You In the social Reagan Shu So if You dynamics that you're describing from like you can infer from this information that is That like focusing on that piece, I'm trying to think of. I'm on all of the things right now. That kind of feeling that you're getting from it how does that intertwine with the top level piece that is +4 00 random summer game like how does that second layer Reagan Shu Yeah You connect? Reagan Shu Can you can you what do you mean by the second layer like so are you saying like what? You So you say you're scrolling you click on it +400 random game OK cool and then you open it up and then you see like the The number, beds and money on each side and you see like a discrepancy that is you just described would be like make you even more enthusiastic to do that but like how Do you know what I'm saying like like how does that give you Reagan Shu Yeah, I see You more conviction towards the bed like beyond when he clicked? Reagan Shu Convinces either convinces or confirms to me the better that there's like value in the pic there's like You Because Reagan Shu because of the factors that include the potential payout being like you know four to one odds which is pretty people, but that's a pretty positive return so You Yeah Reagan Shu that puff I would say the I mean, this is where it comes into like this is where it's tough because it's its personal preference right there might be somebody who there might be somebody who saw the 40% of the money is on You Yeah, exactly exactly Reagan Shu Detroit and it's like that nope get me away from this like I don't want it You Why would they think that? Reagan Shu For the same reason that I might like maybe they think it's rigged maybe they think there's something that they don't know maybe they don't like You They have their reason OK you think there's like like not a deep state but you think there's more going on under the Reagan Shu it maybe there's there's a reason I think You surface and everyone has a unique reason like what is you don't give me yours but like what? What's your take on that you see? what were your numbers like? It was like Reagan Shu Yeah Yeah Alex You a lot of a lot of. Money relative to the number of bets on Detroit, right Reagan Shu Yeah, I would also say that a lot of people don't just just don't like anomalies like that game was an anomaly and that like people if they see an extreme one way or another, they will tend to stay away from it so that's a reason that people might get spooked out by a game like that You When you say stay away like I am, I am so out of it like I don't know that how that would make it like you say out like it would make you stay out of that bet you're staring at right now or like Reagan Shu It would make you make your avoid the game make you avoid that game like if I'm scrolling and I'm I wanna play a bet on a baseball game and I see that and I realize this is not normal. I'm clicking out of that I'm scrolling to the expert and seeing what other options I got. You Yeah, yeah yeah yeah So if you're like a conservative better, you're like oh wow great odds you click on it. You're like this is funky. Reagan Shu Yeah, exactly like you. You almost feel like you're getting trapped one way or another. You That is like an anomaly in the deeper data Reagan Shu So especially especially if it's especially if it's a beginner better who doesn't see it as like oh this might be a short tech where I may actually just get like I might swipe a You Yeah Reagan Shu line that you know comes once every once in a while, a beginner better probably isn't gonna realize that which is why I say if you follow You Yeah Yeah, yeah yeah yeah Reagan Shu movement right like if something's moving like crazy a beginner better might be like I know right so they might You Yeah Reagan Shu but if something steady right at the NFL for the most part is relatively steady those the lines they release on You Yeah Reagan Shu Tuesday and by the time Sunday rolls around the lines rarely changed that much You Yeah Reagan Shu so if there is a lot of change right, like I mean at the beginner better like football because it is pretty steady And it's less to keep up with right, so I think that movement can intimidate some people, especially the beginner better You Yeah, OK I think I think we zoom out again. The most important thing is that everyone has their own philosophy and if you make you make receiving data super simple for them. Reagan Shu Yeah, they'll take it as they want You Easy and then we could even you know, we could take a step further down the line and help you know people can draft up their own like sort of strategies and then then retrieve data the way they want to retrieve it most importantly is If we make data collection, easy for people, though they might actually fucking use it and it kind of captures both the novice and the deep the deeper Reagan Shu Yuck You gambler with the deep state philosophy because if you're just an office and you're like I want you think the same thing I just want the lot and I want I want. I wanna place a bet tonight I wanna drink some beer yeah or if you're fucking if you really want to see the movement and see all the money down and you wanna run? It there so yeah the most important thing is the data is there we shouldn't give it all to someone we should like layer layered and based off of how much they Reagan Shu Yeah, yeah and yeah yeah I would say don't don't You want they don't overall so yeah Reagan Shu overwhelm and don't maybe like don't try to come off like you're trying to help them make a pic like let them make their own empower them to make their own You Yeah Reagan Shu decision which is You So in the normal stats you get if if you're we say you're starting with I see a game and let's go back to the Lakers game cause it's just whatever I see the Lakers game I say hey tell me about the the odds on the Lakers game what do you want to see like what's your homepage look like cause that's very important to be sensitive not overwhelming the user so like what you're thinking in these kind of role-play Reagan Shu Yeah You ways like, what do you? What populate when you ask for that and what doesn't Reagan Shu So if I yeah, what what population is the spread over under in the money line that is like if you click on any bed that's gonna be the main thing there might be some like Especially if you click onto it in ESPN like if you click on that on the ESPN app and click into the game, there will be a little like pie chart that says like percent likely to win that's not related to the bedding. It's like the " ESPN expert analysis so some sites might have like a thing like that as well You Yeah OK Reagan Shu but that all that's not that's never the main like I stay that's always the kind like that's just up to it is it is You Yeah, I got this BS so so yeah so here's my last one for you of like this line of thought Say you know how like you just pull up your phone if Siri actually worked and go hey Siri, you know like you'll send a text whatever Reagan Shu Yeah You So OK Reagan is sitting here right now on a Saturday night. I have no clue who's playing, but if there's someone interesting to bet on, I have a feeling what U2 does right now so first is there do you know for tonight? Is there anything remotely interesting? Reagan Shu I told you define interesting You If you're not aware, it doesn't matter anyways, so we don't know what they are but make it up. What is someone who could be playing tonight that you would want to bet on? Reagan Shu Yeah Yeah Call college Lakers and grizzlies You Lakers grizzlies OK cool so you're sitting on your couch and you're interested in Lakers grizzlies so now tell me the whole story of Reagan is sitting on his couch and he pulls up his phone and says hey Siri Reagan Shu I say hey Siri show me the line for Lakers grizzlies You OK, and then populate the line and you gotta make up for the example whatever the line is to Reagan Shu And then You continue go Reagan Shu Yeah, I'll be it'll be. It'll be a serial populate and it'll probably say OK from a particular source like a particular book rather DraftKings FanDuel ESPN right whatever line that they You Would you odyssey a like a top wait real quick Reagan Shu decide out I would like to see. You on the homepage example with like what do we see? What do we not see with this populate with the lines and you're talking about the different sports books? Would you rather beforehand either select what sports books if any how many like you wanna see do you want populate OK every time your DraftKings and here's FanDuel and Cuva for this piece to what how much populates or like did you choose it to populate it that way? Reagan Shu If if there's a way to cause you know, assuming that not everybody has an account with everything if there's a way to tell what cycle I have an account with the account so I want to account I also have FanDuel I want to see if I have two accounts. I wanna see both because I wanna compare it like if they have if I think I like the Lakers and Lakers minus one and a half says Lakers minus one I'm gonna take the bottle one because that's you know it's helping my cause I believe in so if you have both, I wanna see both but a lot of people don't. You OK so we could do beforehand in this whole processes you you tell the tool they're using through Siri Like literally typing in like to custom instruction I use Bota first and then FanDuel or and we have a default that populates would you say just populate one line or would you say Reagan Shu Yes You populate like I don't know if there's like a three Reagan Shu Yeah You everyone uses or you know what I mean, like what the default be Reagan Shu Yeah, if I would, I would say give pick one that is the default like pick one both state draft case and that's just gonna Caesar sports that better. Caesar sports book is the default yeah like Cesar sports book is the You OK, should we? Reagan Shu default right that you can compare contrast or You Yeah, yeah Reagan Shu whatever I would also say that like it is worth providing the other options because you know somebody may want to create an account at another one right like some You Do we for that that bit of providing the other Reagan Shu people. You options in an interaction standpoint is it more of a set up or a we actually like prompt them? Hey do you want to you know kinda like the opt out on the websites for like do you wanna let us steal all your data like they kinda Reagan Shu Yeah You shove please click except and you have to go through a lot to say no. Should we do something like that words like Reagan Shu Yeah You before you know at some point before this interaction You say all those things or like do we do we make it so it's like hey also would you like to blah blah blah? Reagan Shu Yeah, I'll say the ladder if I also in case like if you were interested in one of these other books, you know view them like here are your options You Yeah, cool OK so you said hey show me the stuff. Keep going with the story. Reagan Shu Yes, I say hey show me the lines for for Lakers grizzlies. I assume it populate and I scroll and I basically. If I if I know, I wanna bet on it right if I didn't, it just comes down. What do I like right if I see it and I like it, I would want to be able to I would want to be able to from there like from that You Yeah Reagan Shu prompt response prompt I wanna be able to click on it and access whatever I like whatever site I bet on like I don't want. I don't wanna read that then go on my phone and say OK now I wanna go make this I wanna be able to click from there yeah You OK So pretend that did happen, but it won't happen because yes but like Reagan Shu Yeah You pretend the once you mentally say I wanna place a bet. It just displaced pretend the actual placing it doesn't matter so so what's your Reagan Shu OK You headspace when you go into saying Siri? Are you what bed are you already thinking of you just waiting to see what populates like what what what is your yeah Reagan Shu Yeah, I'm waiting to see what I'm going to especially if I'm just hanging around on Saturday night like casually I just wanna see what's option. I want to scroll almost like I wanna shop. I wanna see you like what like what matches are five interesting because also you know everybody's gonna have personal preference right like I don't care about that on soccer tonight. I don't care if I'm betting on You OK Reagan Shu basketball like maybe I just wanna bet on the game that's on ESPN then I'll be able to watch right every individual be different so I wanna see the You Yeah, OK so yeah if you say OK the Lakers are the one and you get Reagan Shu options. You your was the three money line over under spread. OK you get money on over under spread after saying hey show me this continue. Reagan Shu So that populate You Yeah, those those three things populate the story ends with OK that's my bad Reagan Shu Yeah, I mean I shop. I click in base and I make the decision right like that in a weird way like if if I see what I see I like I will place it. You So that's all you need to see like it is you see the three and then you're like OK like like Reagan Shu Yeah You that you don't you want the other stuff you want the things we described the money and Betts place splits and all that but at its lowest level that is enough information for you to gain the conviction to then say yes OK Reagan Shu Yeah, at the lowest level, I can see it and like it and make the decision based on that and honestly that's what I usually do like for me You mentally that place Reagan Shu personally, I scroll I see it now if I am like more curious and I wanna look into it I want to feel like I'm making a more informed decision. That's why I want the details of OK. What's the weight of the money? What's the weight of the bet option You Yeah Reagan Shu Like those are the things that I will once like in a way think of it is like OK like I've almost like considered my option right like I've decided that the Lakers grizzly game looks good. It's interesting. It's on TV. I want that on it now I want now I wanna make my pic. OK now I wanna see OK what are people betting on? What's the money on the money on the grizzlies and the money on the You Yeah OK Reagan Shu Lakers or is everybody avoiding the under for some reason right like that's why I would want to look at that and You Cell Reagan Shu that again like when you when you read those things and you see him it'll it. Basically get you to You Yeah Reagan Shu like one pick one choice of the other options that you get and then you make it You OK, so then you say you see the Lakers give me a line that you would say who I want that Reagan Shu Yeah, Lakers -4 1/2 against at home against the grizzlies I like it I'm I'm taking the Lakers -4 1/2 You And who are you taking? OK, take the Lakers 4 1/2 you see that and you go fuck yeah and Reagan Shu Yeah You then I'm guessing there's some people who then or do the lines fluctuate much on a regular season basketball game like would you actually still care to see the other ones? Reagan Shu Yeah, I would because it's like we're not when I think about line fluctuation is what is the lunch start at and where am I looking at it now? You Like the past state is hard but like OK I want the Lakers -4 1/ Reagan Shu Cell You 2 OK cool at that point since it's like you know there's a lot of money in predicting the lines are we thinking that Bodda in DraftKings and Vand all are saying 4 1/2 or five that's gonna be before though he comes before he picked the line with the person before when you say I like that and then see if you can do better well you're probably saying I like that after you see the different line OK so you're like OK I'm intrigued by a 4 1/ 2. Let's see like what what are the lines I can get kind of around this from all the places like you know it seems like Lakers have pretty good odds right now let me see what all the ones I Reagan Shu Yeah You want. It seems like select the sport. Select the game look at all the odds game and then select your line. Based off of the lines, you can get based off lines you can get it Reagan Shu Yeah, so yeah yeah exactly You Yeah, that's that's it Reagan Shu Yeah, yes, love it You So for if we have to present at all if we win one, would you wanna be part of it because we literally couldn't do this without you and two in any form if we do have to present something do you want us to use an alias for your name or is Reagan good? Reagan Shu You can use it. I don't mind at all. I You Our friend Reagan Reagan Shu like you know the biggest the biggest the biggest thing I was I would say is like I like from a value perspective of like the user who would be using it. It's just like it's gonna make you feel like you have an edge right it's gonna make you feel more informed having options is always better right You Yeah Reagan Shu because you know if you think somethings gonna happen and you're willing to place your money on it right you wanna make sure you're getting exactly what you want not with somebody else provides. You Yes Cool all right that's it. That's all we got. Reagan Shu Oh yeah good luck always man. Happy to see you. Good You Thank you, sir keep Reagan Shu luck. ```