diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d219eb0 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,81 @@ +name: Publish Package to PyPI + +permissions: + contents: write + id-token: write + +on: + push: + tags: + - "v*" + workflow_dispatch: + +jobs: + build-and-publish: + name: Build and publish Python distribution to PyPI + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + version: "0.9.7" + enable-cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".python-version" + + - name: Install the project + run: uv sync --no-dev + + - name: Build package + run: uv build + + - name: Verify package contents + run: | + echo "=== Contents of dist/ ===" + ls -lh dist/ + echo "" + echo "=== Wheel contents ===" + unzip -l dist/*.whl || true + echo "" + echo "=== Tarball contents ===" + tar -tzf dist/*.tar.gz || true + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + verbose: true + + - name: Create GitHub Release + uses: ncipollo/release-action@v1 + with: + artifacts: "dist/*" + generateReleaseNotes: true + body: | + ## Installation + ```bash + pip install aieng-platform-onboard + ``` + + ## Usage + + ### Basic Onboarding + ```bash + onboard --bootcamp-name + ``` + + ### With Integration Tests + If you have a test script to verify API keys: + ```bash + onboard --bootcamp-name --skip-test + python path/to/test_keys.py + ``` + + Or let onboard run your test script automatically by placing it in the same directory as the CLI. diff --git a/pyproject.toml b/pyproject.toml index fb9aacb..32fbeba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] -name = "aieng-platform" +name = "aieng-platform-onboard" version = "0.1.0" -description = "Platform infrastructure and tooling for AI Engineering bootcamps" +description = "CLI tool for onboarding participants to AI Engineering bootcamps" readme = "README.md" authors = [{name = "Vector AI Engineering", email = "ai_engineering@vectorinstitute.ai"}] license = "Apache-2.0" @@ -15,16 +15,25 @@ dependencies = [ "weaviate-client>=4.0.0", "requests>=2.31.0", "python-dotenv>=1.0.0", - "pandas>=2.0.0", "rich>=13.0.0", ] +[project.scripts] +onboard = "aieng_platform_onboard.cli:main" + [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] -only-include = ["README.md"] +packages = ["src/aieng_platform_onboard"] + +[tool.hatch.build.targets.sdist] +include = [ + "/src/aieng_platform_onboard", + "/README.md", + "/pyproject.toml", +] [dependency-groups] dev = [ diff --git a/src/aieng_platform_onboard/__init__.py b/src/aieng_platform_onboard/__init__.py new file mode 100644 index 0000000..63250f0 --- /dev/null +++ b/src/aieng_platform_onboard/__init__.py @@ -0,0 +1,12 @@ +""" +aieng-platform-onboard: CLI tool for bootcamp participant onboarding. + +This package provides a command-line interface for onboarding participants +to AI Engineering bootcamps, handling authentication, API key distribution, +and environment setup. +""" + +from aieng_platform_onboard.cli import main + + +__all__ = ["main"] diff --git a/src/aieng_platform_onboard/cli.py b/src/aieng_platform_onboard/cli.py new file mode 100644 index 0000000..c2ebc41 --- /dev/null +++ b/src/aieng_platform_onboard/cli.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python3 +""" +Participant onboarding CLI. + +This module provides the command-line interface for bootcamp participant +onboarding, authenticating participants, fetching their team's API keys +from Firestore, creating a .env file, running integration tests, and marking +them as onboarded. +""" + +import argparse +import subprocess +import sys +from pathlib import Path +from typing import Any + +from rich.panel import Panel + +from aieng_platform_onboard.utils import ( + console, + create_env_file, + fetch_token_from_secret_manager, + get_github_user, + get_global_keys, + get_participant_data, + get_team_data, + initialize_firestore_with_token, + update_onboarded_status, +) + + +def run_integration_test(test_script: Path) -> tuple[bool, str]: + """ + Execute integration test script to verify API keys. + + Parameters + ---------- + test_script : Path + Path to the test script. + + Returns + ------- + tuple[bool, str] + Tuple of (success, output/error). + """ + try: + result = subprocess.run( + [sys.executable, str(test_script)], + check=False, + capture_output=True, + text=True, + timeout=60, + ) + + if result.returncode == 0: + return True, result.stdout + return False, result.stderr if result.stderr else result.stdout + + except subprocess.TimeoutExpired: + return False, "Test timed out after 60 seconds" + except Exception as e: + return False, str(e) + + +def _authenticate_and_connect( + bootcamp_name: str, gcp_project: str, firebase_api_key: str | None +) -> tuple[str, Any] | tuple[None, None]: + """ + Authenticate participant and connect to Firestore. + + Parameters + ---------- + bootcamp_name : str + Name of the bootcamp. + gcp_project : str + GCP project ID. + firebase_api_key : str | None + Firebase Web API key. + + Returns + ------- + tuple[str, any] | tuple[None, None] + Tuple of (github_user, firestore_client) or (None, None) on failure. + """ + # Step 1: Get GitHub username + console.print("\n[bold]Step 1: Identify Participant[/bold]") + github_user = get_github_user() + + if not github_user: + console.print( + "[red]✗ Could not determine GitHub username[/red]\n" + " Please set the GITHUB_USER environment variable" + ) + return None, None + + console.print(f"[green]✓ GitHub User:[/green] {github_user}\n") + + # Step 2: Fetch authentication token + console.print("[bold]Step 2: Fetch Authentication Token[/bold]") + console.print("[cyan]Fetching token from Secret Manager...[/cyan]") + + success, token, error = fetch_token_from_secret_manager( + github_user, bootcamp_name, gcp_project + ) + + if not success or not token: + console.print( + f"[red]✗ Failed to fetch authentication token:[/red]\n" + f" {error}\n\n" + "[yellow]Possible reasons:[/yellow]\n" + " • Tokens not yet generated by admin\n" + " • Incorrect bootcamp name\n" + " • Missing Secret Manager permissions\n\n" + "[dim]Contact bootcamp admin for assistance[/dim]" + ) + return None, None + + console.print("[green]✓ Authentication token retrieved[/green]\n") + + # Step 3: Connect to Firestore + console.print("[bold]Step 3: Connect to Firestore[/bold]") + console.print("[cyan]Initializing secure Firestore connection...[/cyan]") + + try: + db = initialize_firestore_with_token( + token, + gcp_project, + "onboarding", + firebase_api_key=firebase_api_key, + ) + console.print("[green]✓ Connected to Firestore[/green]\n") + return github_user, db + except Exception as e: + console.print(f"[red]✗ Failed to connect to Firestore:[/red] {e}") + return None, None + + +def _fetch_participant_and_team_data( + db: Any, github_user: str +) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]] | tuple[None, None, None]: + """ + Fetch participant, team, and global configuration data. + + Parameters + ---------- + db : any + Firestore client instance. + github_user : str + GitHub username. + + Returns + ------- + tuple[dict, dict, dict] | tuple[None, None, None] + Tuple of (participant_data, team_data, global_keys) or (None, None, None). + """ + # Step 4: Fetch participant data + console.print("[bold]Step 4: Fetch Your Profile[/bold]") + console.print("[cyan]Reading participant data...[/cyan]") + + participant_data = get_participant_data(db, github_user) + + if not participant_data: + console.print( + f"[red]✗ Participant profile not found for '{github_user}'[/red]\n" + "[dim]Contact bootcamp admin to add you to the participant list[/dim]" + ) + return None, None, None + + team_name = participant_data.get("team_name") + if not team_name: + console.print("[red]✗ No team assigned to your profile[/red]") + return None, None, None + + console.print("[green]✓ Profile found[/green]") + console.print(f" Team: [yellow]{team_name}[/yellow]\n") + + # Step 5: Fetch team data (includes API keys) + console.print("[bold]Step 5: Fetch Team API Keys[/bold]") + console.print(f"[cyan]Reading team data for '{team_name}'...[/cyan]") + + team_data = get_team_data(db, team_name) + + if not team_data: + console.print( + f"[red]✗ Team data not found for '{team_name}'[/red]\n" + "[dim]Contact bootcamp admin[/dim]" + ) + return None, None, None + + # Check if team has required keys + missing_keys = [] + if not team_data.get("openai_api_key"): + missing_keys.append("OPENAI_API_KEY (Gemini)") + + if missing_keys: + console.print( + "[yellow]⚠ Warning: Team is missing some API keys:[/yellow]\n" + f" {', '.join(missing_keys)}\n" + "[dim]Onboarding will continue, but some features may not work[/dim]\n" + ) + + console.print("[green]✓ Team API keys retrieved[/green]\n") + + # Step 6: Fetch global shared keys + console.print("[bold]Step 6: Fetch Global Configuration[/bold]") + console.print("[cyan]Reading shared keys...[/cyan]") + + global_keys = get_global_keys(db) + + if not global_keys: + console.print( + "[red]✗ Global keys not found[/red]\n[dim]Contact bootcamp admin[/dim]" + ) + return None, None, None + + console.print("[green]✓ Global configuration retrieved[/green]\n") + + return participant_data, team_data, global_keys + + +def _setup_environment( + output_dir: str, team_data: dict[str, Any], global_keys: dict[str, Any] +) -> Path | None: + """ + Create .env file with API keys and configuration. + + Parameters + ---------- + output_dir : str + Directory where .env file should be created. + team_data : dict[str, any] + Team data containing API keys. + global_keys : dict[str, any] + Global configuration keys. + + Returns + ------- + Path | None + Path to created .env file or None on failure. + """ + console.print("[bold]Step 7: Create Environment File[/bold]") + output_path = Path(output_dir) / ".env" + console.print(f"[cyan]Creating .env file at: {output_path}[/cyan]") + + if output_path.exists(): + console.print( + "[yellow]⚠ .env file already exists, will be overwritten[/yellow]" + ) + + success_create = create_env_file(output_path, team_data, global_keys) + + if not success_create: + console.print("[red]✗ Failed to create .env file[/red]") + return None + + console.print("[green]✓ .env file created successfully[/green]\n") + return output_path + + +def _run_tests_and_finalize(db: Any, github_user: str, skip_test: bool) -> bool: + """ + Run integration tests and update onboarded status. + + Parameters + ---------- + db : any + Firestore client instance. + github_user : str + GitHub username. + skip_test : bool + Whether to skip integration tests. + + Returns + ------- + bool + True if successful, False otherwise. + """ + # Step 8: Run integration test + if not skip_test: + console.print("[bold]Step 8: Run Integration Test[/bold]") + console.print("[cyan]Testing API keys...[/cyan]") + + # Determine test script path - look in the package directory + package_dir = Path(__file__).parent + test_script = package_dir / "test_keys.py" + + if not test_script.exists(): + console.print("[yellow]⚠ Test script not found, skipping tests[/yellow]\n") + else: + success_test, output = run_integration_test(test_script) + + if success_test: + console.print("[green]✓ All API keys tested successfully[/green]\n") + else: + console.print( + "[red]✗ Integration test failed:[/red]\n" + f"[dim]{output}[/dim]\n\n" + "[yellow]Your .env file was created, but some keys may not work.[/yellow]\n" + "[dim]Contact bootcamp admin if you need assistance[/dim]\n" + ) + return False + else: + console.print("[dim]Integration test skipped[/dim]\n") + + # Step 9: Update onboarded status + console.print("[bold]Step 9: Mark as Onboarded[/bold]") + console.print("[cyan]Updating your status...[/cyan]") + + success_update, error_update = update_onboarded_status(db, github_user) + + if not success_update: + console.print( + f"[yellow]⚠ Failed to update onboarded status:[/yellow] {error_update}\n" + "[dim]Your .env file is ready to use, but status was not updated[/dim]\n" + ) + else: + console.print("[green]✓ Marked as onboarded[/green]\n") + + return True + + +def main() -> int: + """ + Onboard bootcamp participants with team-specific API keys. + + Returns + ------- + int + Exit code (0 for success, 1 for failure). + """ + parser = argparse.ArgumentParser( + description="Bootcamp participant onboarding script", + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "--bootcamp-name", + type=str, + required=True, + help="Name of the bootcamp (e.g., fall-2025)", + ) + parser.add_argument( + "--gcp-project", + type=str, + default="coderd", + help="GCP project ID (default: coderd)", + ) + parser.add_argument( + "--output-dir", + type=str, + default=".", + help="Directory where .env file should be created (default: current directory)", + ) + parser.add_argument( + "--skip-test", + action="store_true", + help="Skip integration test", + ) + parser.add_argument( + "--firebase-api-key", + type=str, + help="Firebase Web API key for token exchange (or set FIREBASE_WEB_API_KEY env var)", + ) + + args = parser.parse_args() + + # Print header + console.print( + Panel.fit( + "[bold cyan]Bootcamp Participant Onboarding[/bold cyan]\n" + "Setup your environment with team-specific API keys", + border_style="cyan", + ) + ) + + # Authenticate and connect + github_user, db = _authenticate_and_connect( + args.bootcamp_name, args.gcp_project, args.firebase_api_key + ) + if not github_user or not db: + return 1 + + # Fetch data + participant_data, team_data, global_keys = _fetch_participant_and_team_data( + db, github_user + ) + if not participant_data or not team_data or not global_keys: + return 1 + + # Setup environment + output_path = _setup_environment(args.output_dir, team_data, global_keys) + if not output_path: + return 1 + + # Run tests and finalize + if not _run_tests_and_finalize(db, github_user, args.skip_test): + return 1 + + # Final summary + console.print( + Panel.fit( + "[green bold]✓ ONBOARDING COMPLETE[/green bold]\n\n" + f"Your .env file is ready at: [cyan]{output_path}[/cyan]\n\n" + "[bold]Next steps:[/bold]\n" + "1. Source the environment file:\n" + f" [cyan]source {output_path}[/cyan]\n" + "2. Start building!\n\n" + "[dim]If you encounter any issues, contact your bootcamp admin[/dim]", + border_style="green", + title="Success", + ) + ) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/aieng_platform_onboard/utils.py b/src/aieng_platform_onboard/utils.py new file mode 100644 index 0000000..6af5b5c --- /dev/null +++ b/src/aieng_platform_onboard/utils.py @@ -0,0 +1,391 @@ +"""Shared utilities for participant onboarding scripts.""" + +import os +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +import requests +from google.cloud import firestore, secretmanager # type: ignore[attr-defined] +from google.oauth2 import credentials as oauth2_credentials +from rich.console import Console + + +# Constants +FIRESTORE_PROJECT_ID = "coderd" +FIRESTORE_DATABASE_ID = "onboarding" +FIREBASE_AUTH_EXCHANGE_URL = ( + "https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken" +) + +# Global console instance for rich output +console = Console() + + +def get_console() -> Console: + """ + Get the global console instance for rich output. + + Returns + ------- + Console + Rich Console instance for formatted output. + """ + return console + + +def get_github_user() -> str | None: + """ + Get GitHub username from environment variable. + + Returns + ------- + str | None + GitHub username if found, None otherwise. + """ + github_user = os.environ.get("GITHUB_USER") + if not github_user: + # Try alternative environment variables + github_user = os.environ.get("GH_USER") or os.environ.get("USER") + return github_user + + +def fetch_token_from_secret_manager( + github_handle: str, bootcamp_name: str, project_id: str +) -> tuple[bool, str | None, str | None]: + """ + Fetch Firebase token from GCP Secret Manager. + + Parameters + ---------- + github_handle : str + GitHub handle of the participant. + bootcamp_name : str + Name of the bootcamp. + project_id : str + GCP project ID. + + Returns + ------- + tuple[bool, str | None, str | None] + Tuple of (success, token_value, error_message). + """ + try: + client = secretmanager.SecretManagerServiceClient() + secret_name = f"{bootcamp_name}-token-{github_handle}" + name = f"projects/{project_id}/secrets/{secret_name}/versions/latest" + + response = client.access_secret_version(request={"name": name}) + token = response.payload.data.decode("UTF-8") + + return True, token, None + + except Exception as e: + return False, None, str(e) + + +def exchange_custom_token_for_id_token( + custom_token: str, api_key: str +) -> tuple[bool, str | None, str | None]: + """ + Exchange Firebase custom token for ID token via Firebase Auth REST API. + + Parameters + ---------- + custom_token : str + Firebase custom token. + api_key : str + Firebase Web API key. + + Returns + ------- + tuple[bool, str | None, str | None] + Tuple of (success, id_token, error_message). + """ + try: + url = f"{FIREBASE_AUTH_EXCHANGE_URL}?key={api_key}" + payload = {"token": custom_token, "returnSecureToken": True} + + response = requests.post(url, json=payload, timeout=10) + + if response.status_code != 200: + error_msg = response.json().get("error", {}).get("message", "Unknown error") + return False, None, f"Firebase Auth API error: {error_msg}" + + data = response.json() + id_token = data.get("idToken") + + if not id_token: + return False, None, "No ID token in response" + + return True, id_token, None + + except Exception as e: + return False, None, str(e) + + +def initialize_firestore_with_token( + custom_token: str, + project_id: str, + database_id: str, + firebase_api_key: str | None = None, +) -> firestore.Client: + """ + Initialize Firestore client with Firebase authentication token. + + This function exchanges the Firebase custom token for an ID token and uses it + to authenticate Firestore requests, ensuring security rules are enforced. + + Parameters + ---------- + custom_token : str + Firebase custom token from Secret Manager. + project_id : str + GCP project ID. + database_id : str + Firestore database ID. + firebase_api_key : str | None, optional + Firebase Web API key for token exchange. If not provided, will attempt + to read from FIREBASE_WEB_API_KEY environment variable. + + Returns + ------- + firestore.Client + Authenticated Firestore client with security rules enforced. + + Raises + ------ + Exception + If initialization or token exchange fails. + """ + try: + # Get Firebase Web API key + if not firebase_api_key: + firebase_api_key = os.environ.get("FIREBASE_WEB_API_KEY") + + if not firebase_api_key: + raise Exception( + "Firebase Web API key required for token exchange. " + "Set FIREBASE_WEB_API_KEY environment variable or pass as parameter." + ) + + # Exchange custom token for ID token + console.print("[dim]Exchanging custom token for ID token...[/dim]") + success, id_token, error = exchange_custom_token_for_id_token( + custom_token, firebase_api_key + ) + + if not success or not id_token: + raise Exception(f"Failed to exchange custom token: {error}") + + # Create OAuth2 credentials with the ID token + # Firebase ID tokens can be used as bearer tokens for Google APIs + creds = oauth2_credentials.Credentials(token=id_token) # type: ignore[no-untyped-call] + + # Initialize Firestore client with the credentials + return firestore.Client( + project=project_id, database=database_id, credentials=creds + ) + + except Exception as e: + raise Exception(f"Failed to initialize Firestore client: {e}") from e + + +def get_participant_data( + db: firestore.Client, github_handle: str +) -> dict[str, Any] | None: + """ + Retrieve participant document from Firestore. + + Parameters + ---------- + db : firestore.Client + Firestore client instance. + github_handle : str + GitHub handle of the participant. + + Returns + ------- + dict[str, Any] | None + Participant data or None if not found. + """ + try: + doc_ref = db.collection("participants").document(github_handle) + doc = doc_ref.get() + + if not doc.exists: + return None + + return doc.to_dict() + + except Exception as e: + console.print(f"[red]✗ Failed to fetch participant data:[/red] {e}") + return None + + +def get_team_data(db: firestore.Client, team_name: str) -> dict[str, Any] | None: + """ + Retrieve team document from Firestore. + + Parameters + ---------- + db : firestore.Client + Firestore client instance. + team_name : str + Name of the team. + + Returns + ------- + dict[str, Any] | None + Team data or None if not found. + """ + try: + doc_ref = db.collection("teams").document(team_name) + doc = doc_ref.get() + + if not doc.exists: + return None + + return doc.to_dict() + + except Exception as e: + console.print(f"[red]✗ Failed to fetch team data:[/red] {e}") + return None + + +def get_global_keys(db: firestore.Client) -> dict[str, Any] | None: + """ + Retrieve global keys from Firestore. + + Parameters + ---------- + db : firestore.Client + Firestore client instance. + + Returns + ------- + dict[str, Any] | None + Global keys data or None if not found. + """ + try: + doc_ref = db.collection("global_keys").document("bootcamp-shared") + doc = doc_ref.get() + + if not doc.exists: + return None + + return doc.to_dict() + + except Exception as e: + console.print(f"[red]✗ Failed to fetch global keys:[/red] {e}") + return None + + +def create_env_file( + output_path: Path, + team_data: dict[str, Any], + global_keys: dict[str, Any], +) -> bool: + """ + Create .env file with API keys and configuration. + + Parameters + ---------- + output_path : Path + Path where .env file should be created. + team_data : dict[str, Any] + Team data containing team-specific keys. + global_keys : dict[str, Any] + Global keys shared across all participants. + + Returns + ------- + bool + True if successful, False otherwise. + """ + try: + # Build .env content + env_content = "#!/bin/bash\n" + env_content += "# OpenAI-compatible LLM (Gemini)\n" + env_content += 'OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"\n' + env_content += f'OPENAI_API_KEY="{team_data.get("openai_api_key", "")}"\n\n' + + env_content += "# Embedding model\n" + env_content += ( + f'EMBEDDING_BASE_URL="{global_keys.get("EMBEDDING_BASE_URL", "")}"\n' + ) + env_content += ( + f'EMBEDDING_API_KEY="{global_keys.get("EMBEDDING_API_KEY", "")}"\n\n' + ) + + env_content += "# LangFuse\n" + env_content += ( + f'LANGFUSE_SECRET_KEY="{team_data.get("langfuse_secret_key", "")}"\n' + ) + env_content += ( + f'LANGFUSE_PUBLIC_KEY="{team_data.get("langfuse_public_key", "")}"\n' + ) + env_content += f'LANGFUSE_HOST="{global_keys.get("LANGFUSE_HOST", "")}"\n\n' + + env_content += "# Weaviate\n" + env_content += ( + f'WEAVIATE_HTTP_HOST="{global_keys.get("WEAVIATE_HTTP_HOST", "")}"\n' + ) + env_content += ( + f'WEAVIATE_GRPC_HOST="{global_keys.get("WEAVIATE_GRPC_HOST", "")}"\n' + ) + env_content += f'WEAVIATE_API_KEY="{global_keys.get("WEAVIATE_API_KEY", "")}"\n' + env_content += ( + f'WEAVIATE_HTTP_PORT="{global_keys.get("WEAVIATE_HTTP_PORT", "")}"\n' + ) + env_content += ( + f'WEAVIATE_GRPC_PORT="{global_keys.get("WEAVIATE_GRPC_PORT", "")}"\n' + ) + env_content += ( + f'WEAVIATE_HTTP_SECURE="{global_keys.get("WEAVIATE_HTTP_SECURE", "")}"\n' + ) + env_content += ( + f'WEAVIATE_GRPC_SECURE="{global_keys.get("WEAVIATE_GRPC_SECURE", "")}"\n' + ) + + # Write to file + with open(output_path, "w") as f: + f.write(env_content) + + return True + + except Exception as e: + console.print(f"[red]✗ Failed to create .env file:[/red] {e}") + return False + + +def update_onboarded_status( + db: firestore.Client, github_handle: str +) -> tuple[bool, str | None]: + """ + Update participant's onboarded status in Firestore. + + Parameters + ---------- + db : firestore.Client + Firestore client instance. + github_handle : str + GitHub handle of the participant. + + Returns + ------- + tuple[bool, str | None] + Tuple of (success, error_message). + """ + try: + doc_ref = db.collection("participants").document(github_handle) + doc_ref.update( + { + "onboarded": True, + "onboarded_at": datetime.now(timezone.utc), + } + ) + return True, None + + except Exception as e: + return False, str(e) diff --git a/uv.lock b/uv.lock index 866c1ec..a801099 100644 --- a/uv.lock +++ b/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ ] [[package]] -name = "aieng-platform" +name = "aieng-platform-onboard" version = "0.1.0" source = { editable = "." } dependencies = [ @@ -17,7 +17,6 @@ dependencies = [ { name = "google-cloud-firestore" }, { name = "google-cloud-secret-manager" }, { name = "openai" }, - { name = "pandas" }, { name = "python-dotenv" }, { name = "requests" }, { name = "rich" }, @@ -55,7 +54,6 @@ requires-dist = [ { name = "google-cloud-firestore", specifier = ">=2.18.0" }, { name = "google-cloud-secret-manager", specifier = ">=2.20.0" }, { name = "openai", specifier = ">=1.0.0" }, - { name = "pandas", specifier = ">=2.0.0" }, { name = "python-dotenv", specifier = ">=1.0.0" }, { name = "requests", specifier = ">=2.31.0" }, { name = "rich", specifier = ">=13.0.0" }, @@ -1511,69 +1509,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, ] -[[package]] -name = "numpy" -version = "2.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, - { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, - { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, - { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, - { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, - { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, - { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, - { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, - { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, - { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, - { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, - { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, - { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, - { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, - { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, - { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, - { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, - { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, - { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, - { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, - { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, - { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, - { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, - { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, - { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, - { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, - { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, - { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, - { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, - { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, - { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, - { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, - { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, - { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, - { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, - { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, - { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, - { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, - { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, - { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, - { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, - { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, - { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, -] - [[package]] name = "openai" version = "2.6.1" @@ -1620,53 +1555,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, ] -[[package]] -name = "pandas" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, -] - [[package]] name = "parso" version = "0.8.5" @@ -2122,15 +2010,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, ] -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -2459,15 +2338,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] -[[package]] -name = "tzdata" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, -] - [[package]] name = "urllib3" version = "2.5.0"